BadPaddingException loading p12 keystore - java

When executing the following code:
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(new FileInputStream(keyStore), storePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPassword.toCharArray());
I get an exception:
java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
This was originally from a JKS keystore converted to a PKCS12 keystore using keytool. I tried creating a new fresh PKCS12 keystore but no luck.

JKS supports using two passwords, one for the store and one for the key inside. PKCS12 uses the same password for both. When using the keytool, you'll get a warning about this.
When migrating to the new keystore, the code will likely continue using one password for the keystore, and another (different) password for the key, though now that won't work.
Java 9 gives a much better exception message around this indicating it might arise from a bad key during decryption.
In this case, make sure to pass in a key password that matches the store password.

Related

Using java keytool to encrypt imported paswords with PBEWithHmacSHA256AndAES_128

I am looking to store sensitive passwords in a java keystore using java keytool's importpass. I am using Oracle java version 1.8.0_212, and cannot upgrade from java 8 at the moment.
I have created a PKCS12 keystore with the following command:
keytool -keystore test-keystore.p12 -genkey -storetype PKCS12 -alias test
I then imported a password into the keystore using:
keytool -importpass -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12
This worked, but this uses the encryption algorithm "PBEWithMD5AndDES" by default, which isn't particularly secure. I am now trying to use "PBEWithHmacSHA256AndAES_128" from the Java Security Standard Algorithm Names doc but having issues getting this to work.
I've tried specifying the keyalg like this:
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
and while this doesn't cause an error, it doesn't seem to actually affect the output. The secret is key still generated with PBEWithMD5AndDES:
D:\temp>keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
Enter keystore password:
Enter the password to be stored:
Re-enter password:
Generated PBEWithMD5AndDES secret key
[Storing test-keystore.p12]
I can see examples, such as in Java Keystores the Gory Details, of people using KeyStore.PasswordProtection to use algorithms like this, but I wanted to use the keytool if possible.
Am I missing something key here or trying to do something silly?
EDIT:
Tried to do something similar programmatically to figure out what I'm doing wrong and had no luck using PKCS12 keystores with this algorithm. However, jceks seems to work. I think this is to do with it using the SunJCE provider instead. Is there some parameter I am missing to get this algorithm working with pcks12? Or is there some other approach I could take?
I have provided some simple demo code below. If you swap "JCEKS" for "PKCS12" it will throw java.security.NoSuchAlgorithmException: unrecognized algorithm name: PBEWithHmacSHA256AndAES_128.
Demo Code:
static void encrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(null, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
SecretKey pbeKey = pbeKeyFactory.generateSecret(new PBEKeySpec("testpassword".toCharArray()));
keyStore.setEntry(SECRET_KEY_ALIAS, new KeyStore.SecretKeyEntry(
pbeKey), keyStorePP);
FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
keyStore.store(outputStream, "changeit".toCharArray());
}
static void decrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
FileInputStream fileInputStream = new FileInputStream(FILE_PATH);
keyStore.load(fileInputStream, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
KeyStore.SecretKeyEntry ske =
(KeyStore.SecretKeyEntry)keyStore.getEntry(SECRET_KEY_ALIAS, keyStorePP);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
ske.getSecretKey(),
PBEKeySpec.class);
System.out.println(new String(keySpec.getPassword()));
}
You need to keep separate the algorithm a key is for and the way it is protected.
A Java keystore (depending on type, including JCEKS and PKCS12 but not JKS) can contain three kinds of entries. A SecretKeyEntry in general contains a secret key which is typically valid for a particular cipher algorithm, identified in the SecretKey object within the entry. But for password-based algorithms, the 'key' object is actually a generic 'PBEKey' which contains a password and AFAICT can be used for any PBE algorithm. It appears the 'algorithm' for such keys is actually stored as PBEwithMD5andDES, I suspect because that's the only scheme from original PKCS5 (i.e. PBES1) Java implements. However, my Oracle 8u212 keytool says more generically Generated PBE secret key in this case, not PBEwithMD5andDES as you show.
The stored secret key (and stored private key also) can be and normally is 'protected' in the keystore by being encrypted with a password-based encryption algorithm; this 'protection' algorithm is different from and in general not related to the algorithm(s) that the key or password will be used with after retrieval.
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 attempts to specify the algorithm the key-really-password is nominally to be used with when retrieved, but not how it is protected, which for PKCS12 in most older versions including yours is actually PBE-SHA1-TripleDES (the same as for privatekeys) -- you can see this, with a little work, using OpenSSL. And in my 8u212 keytool specifying -keyalg fails for PKCS12 with an exception claiming it can't protect because there are no parameters for that algorithm (although I think the problem is serializing, not actually encrypting) much as you say your code does for PKCS12, but Java does have these parameters -- although in a different provider (SunJCE vs SunJSSE) which might be messing it up somehow; as you say JCEKS is in SunJCE.
Similarly your code generates a key to be used with that algorithm, but protected the default way. To change the protection you need to specify it in PasswordProtection, as stated in the page you link; see https://stackoverflow.com/a/47389384/ (by the same author) for an example.
Recent versions of Java -- 8u301 and 11.0.12 up -- have a new feature to configure the protection algorithms for PKCS12 with security properties in the java.security file which is in JRE/lib/security for 8 down or JRE/conf/security for 9 up. This applies to both code you write if not overridden as above, and to keytool (which can't override). However, those versions (except for 12 through 16, which are now EOL) also now default to PBEwithHmacSHA256andAES_256 which is already better than you asked for, so you probably don't need to change anything -- just use 8u301 up.
Note JCEKS uses a Sun-defined PBE-MD5-TripleDES similar to, but not, PBES1, while JKS doesn't support secretkey entries but for privatekey entries uses a deliberately weak (ITAR-friendly back in the 1990s) Sun-custom algorithm; these cannot be changed.

How do you convert PKCS#12 String to Certificate and PrivateKey?

I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.

Inspecting Certificate gives me question mark symbol

I am trying to read a private key entry of a PKCS12 file in order to get a VPN connection working in Android.
This is my code:
String password = "password";
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("/storage/emulated/0/Download/certfile.p12"), password.toCharArray());
Enumeration<String> aliases = keystore.aliases();
Log.d("key", aliases.nextElement());
Now in my Aliases I see the private key Entry with an Alias which seems to contain invalid characters. Because it's showing me in Debug Inspection and also in the console "ALIAS��"
This makes it impossible to load the Alias or check if the keystore contains it.
When I inspect the keystore with the command line with the keytool I get the Aliasname "alias" in small letters.

Create signature with bouncycastle api. Key always null

I'm using bouncycastle for generating detached signature for XML's signing. For key initialize I use this code:
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(CERT_ALIAS, KEYSTORE_PWD.toCharArray());
I have JKS keystore with certificate. But if I do this:
Key key = ks.getKey(CERT_ALIAS, KEYSTORE_PWD.toCharArray());
key stay always null and I have InvalidKeyException
Where's my mistake? I new in crypto
I can't comment due to too low reputation. So I'll answer/edit instead.
The above example works fine, the error is probably in one of the constants being used. What are they, and what is the exact error you're getting?
I mean something like: java.security.InvalidKeyException: Illegal key size
Here is the working example I tried while loading a KeyStore from a file:
'secret1' is the store password, 'secret2' is the key password and 'myKey' is the key alias.
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(keyStoreFile.getAbsolutePath()), "secret1".toCharArray());
Key key = keyStore.getKey("myKey", "secret2".toCharArray());

Storing an X.509 certificate into a keystore using java code

I have an X.509 certificate created using bouncycastle library. How can I store it into a java Keystore?
I tried this code
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// get user password and file input stream
char[] password = getPassword();
java.io.FileInputStream fis =
new java.io.FileInputStream("keyStoreName");
ks.load(fis, password);
fis.close();
I found this code here, but the key store created using this way does not work with keytool, it tells me the keystore is corrupted.
Use KeyStore.setCertificateEntry(alias, cert) and give it an alias name of your choice. Then, use KeyStore.store(...) to save the keystore (typically using a FileOutputStream).

Categories