Question

My task is the following:

  • Retrieve my public and private key from the keystore I created.
  • Use these keys to encrypt a paragraph using my RSA 2048-bit public key.
  • Digitally sign the result using the DSA-SHA-1 signature algorithm.
  • Save the digital signature output on a file called output.dat.

The program below is throwing error : "java.security.InvalidKeyException: No installed provider supports this key: sun.security.provider.DSAPublicKeyImpl".

import java.security.*;
import java.security.KeyStore.*;
import java.io.*;
import java.security.PublicKey;
import java.security.PrivateKey;
import javax.crypto.Cipher;
import java.nio.charset.*;
import sun.security.provider.*;
import  javax.crypto.*;

public class Code {

/**
 * @param args the command line arguments
 */
    public static void main(String[] args) {

        try {

            /* getting data for keystore */

            File file = new File(System.getProperty("user.home") + File.separatorChar + ".keystore");
            FileInputStream is = new FileInputStream(file);
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());

            /*Information for certificate to be generated */ 
            String password = "abcde";
            String alias = "mykeys";
            String alias1 = "skeys";

            String filepath ="C:\\email.txt";

            /* getting the key*/
            keystore.load(is, password.toCharArray());
            PrivateKey key = (PrivateKey)keystore.getKey(alias, "bemylife".toCharArray());
            //PrivateKey key = cert1.getPrivateKey();
            //PublicKey key1= (PrivateKey)key;

            /* Get certificate of public key */
            java.security.cert.Certificate cert = keystore.getCertificate(alias); 

            /* Here it prints the public key*/
            System.out.println("Public Key:");
            System.out.println(cert.getPublicKey());

            /* Here it prints the private key*/
            System.out.println("\nPrivate Key:");
            System.out.println(key);

            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE,cert.getPublicKey());

            String cleartextFile = "C:\\email.txt";
            String ciphertextFile = "D:\\ciphertextRSA.png";

            FileInputStream fis = new FileInputStream(cleartextFile);
            FileOutputStream fos = new FileOutputStream(ciphertextFile);
            CipherOutputStream cos = new CipherOutputStream(fos, cipher);

            byte[] block = new byte[32];
            int i;
            while ((i = fis.read(block)) != -1) {
                cos.write(block, 0, i);
            }
            cos.close();


            /* computing the signature*/
            Signature dsa = Signature.getInstance("SHA1withDSA", "SUN");
            dsa.initSign(key);
            FileInputStream f = new FileInputStream(ciphertextFile);
            BufferedInputStream in = new BufferedInputStream(f);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) >= 0) {
               dsa.update(buffer, 0, len);
           };
           in.close();

           /* Here it prints the signature*/
           System.out.println("Digital Signature :");
           System.out.println( dsa.sign());

           /* Now Exporting Certificate */
           System.out.println("Exporting Certificate. ");
           byte[] buffer_out = cert.getEncoded();
           FileOutputStream os = new FileOutputStream(new File("d:\\signedcetificate.cer"));
           os.write(buffer_out);
           os.close();

           /* writing signature to output.dat file */
           byte[] buffer_out1 = dsa.sign();
           FileOutputStream os1 = new FileOutputStream(new File("d:\\output.dat"));
           os1.write(buffer_out1);
           os1.close();

       } catch (Exception e) {System.out.println(e);}

   }
}
Was it helpful?

Solution 2

The problem is that a DSA key is unsuitable for RSA encryption. You need an RSA key for encryption, maybe you can switch your signature algorithm to RSA/SHA1 to avoid the need for two keys..

OTHER TIPS

You have to read it from the keystore file (which probably ends in .jks) into a java.security.KeyStore object.

/**
 * Reads a Java keystore from a file.
 * 
 * @param keystoreFile
 *          keystore file to read
 * @param password
 *          password for the keystore file
 * @param keyStoreType
 *          type of keystore, e.g., JKS or PKCS12
 * @return the keystore object
 * @throws KeyStoreException
 *           if the type of KeyStore could not be created
 * @throws IOException
 *           if the keystore could not be loaded
 * @throws NoSuchAlgorithmException
 *           if the algorithm used to check the integrity of the keystore
 *           cannot be found
 * @throws CertificateException
 *           if any of the certificates in the keystore could not be loaded
 */
public static KeyStore loadKeyStore(final File keystoreFile,
    final String password, final String keyStoreType)
    throws KeyStoreException, IOException, NoSuchAlgorithmException,
    CertificateException {
  if (null == keystoreFile) {
    throw new IllegalArgumentException("Keystore url may not be null");
  }
  LOG.debug("Initializing key store: {}", keystoreFile.getAbsolutePath());
  final URI keystoreUri = keystoreFile.toURI();
  final URL keystoreUrl = keystoreUri.toURL();
  final KeyStore keystore = KeyStore.getInstance(keyStoreType);
  InputStream is = null;
  try {
    is = keystoreUrl.openStream();
    keystore.load(is, null == password ? null : password.toCharArray());
    LOG.debug("Loaded key store");
  } finally {
    if (null != is) {
      is.close();
    }
  }
  return keystore;
}

Once you have the KeyStore, you can get to the Certificate and the public and private keys.

But using that to sign text and save it in a file is more involved, and easy to do wrong. Take a look at Sign string using given Public Key and replace the getKeyPair method with one that uses the KeyStore. Something along the lines of

public static KeyPair getKeyPair(final KeyStore keystore, 
    final String alias, final String password) {
  final Key key = (PrivateKey) keystore.getKey(alias, password.toCharArray());

  final Certificate cert = keystore.getCertificate(alias);
  final PublicKey publicKey = cert.getPublicKey();

  return KeyPair(publicKey, (PrivateKey) key);
}

(obviously a little rougher, I didn't have a sample handy)

trusted.load(in, ((PBCApplication) context.getApplicationContext()).getBuildSettings().getCertificatePass());
Enumeration enumeration = trusted.aliases();

while (enumeration.hasMoreElements()) {
    String alias = (String) enumeration.nextElement();
    System.out.println("alias name: " + alias);
    Certificate certificate = trusted.getCertificate(alias);
    certificate.getPublicKey();
}

In Spring Boot(my example is using 2.4.x), you can use a bean for your KeyStore, like so:

import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
public class KeyStoreConfiguration {

    private static final String KEY_STORE = "keystore.p12";
    private static final String KEY_STORE_TYPE = "PKCS12";
    private static final String KEY_STORE_PASSWORD = "password";
    
    @Bean
    public KeyStore keyStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
        KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
        keyStore.load(new ClassPathResource(KEY_STORE).getInputStream(), KEY_STORE_PASSWORD.toCharArray());
        
        return keyStore;
    }
}

and you can then retrieve the public and private key like so:

import java.security.PrivateKey;
import java.security.PublicKey;

PrivateKey privateKey = (PrivateKey) keyStore.getKey("my-alias", "my-password".toCharArray());
PublicKey publicKey = keyStore.getCertificate("my-alias").getPublicKey();

I don't have the Java code stored at the top of my brain, but some general sanity checks are:

  • is the public certificate you want stored where you want it? In particular, my recollection is that the certificate with the public key and the private key are stored together under a single alias, so the two alias setting you have there seems really odd. Try storing both under the same alias and referencing it in both the private and public key calls.

  • can you get anything else out of the certificate - for example, subject DN or issuer DN are both must-have fields in a certificate. That gives you a good proof that the certificate is being read as expected.

  • in almost any crypto transaction, be very careful with how you read from files and transfer your encoding methods. If you've created your File IO and pulled from it in a weird way, you can corrupt the encoding of the key material. That's a last thing to check - usually Java and JKS haven't been so bad for this, but it happens. On the same vein, be clear about the format of the file - JKS files are different from PKCS 12 files, for example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top