I am trying to generate a key using Java. To be honest I am not that experienced with keys, password, ciphers and encryption.
And from whatever I have searched from this site, I see it as a very common problem. I did some reading and came up with this code that I wrote:
Security.setProperty("crypto.policy", "unlimited");
String valueToEncode = "some_random_text";
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[256];
secureRandom.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray(), salt, 65536, 256); // AES-256
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] ivBytes = new byte[16];
secureRandom.nextBytes(ivBytes);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
byte[] finalCiphertext = new byte[encValue.length + 2 * 16];
System.arraycopy(ivBytes, 0, finalCiphertext, 0, 16);
System.arraycopy(salt, 0, finalCiphertext, 16, 16);
System.arraycopy(encValue, 0, finalCiphertext, 32, encValue.length);
System.out.println(finalCiphertext.toString());
This is modified from an answer that I saw on another post. But I still get the "invalid length" error.
The error that I get is:
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 20 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:93)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.implInit(Cipher.java:805)
at javax.crypto.Cipher.chooseProvider(Cipher.java:863)
at javax.crypto.Cipher.init(Cipher.java:1395)
at javax.crypto.Cipher.init(Cipher.java:1326)
at com.att.logicalprovisioning.simulators.Trial.main(Trial.java:47)
Trial.java:47 being the line: cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
Is there a one-size fits all solution to this? Or is it just my lack of understanding?
Any help would be appreciated.
Your key is 20 bytes long because secretKeyFactory.generateSecret(keySpec).getEncoded() returns the password some_random_password.
An easy way to fix the code is to use the key derivation PBKDF2WithHmacSHA512 instead of PBEWITHHMACSHA512ANDAES_256. This generates a key of the specified length based on the password and salt:
...
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
...
However, PBEWITHHMACSHA512ANDAES_256 can also be applied. This algorithm specifies a key derivation with PBKDF2WithHmacSHA512 and subsequent AES encryption. The implementation is functionally identical to yours, but requires a few more changes to the code:
...
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec);
PBEKeySpec keySpec = new PBEKeySpec("some_random_password".toCharArray());
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
SecretKey secretKey = kf.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] encValue = cipher.doFinal(valueToEncode.getBytes(StandardCharsets.UTF_8));
...
Two other issues are:
You are using a 256 bytes salt, but only storing 16 bytes when concatenating. To be consistent with the concatenation, apply a 16 bytes salt: byte[] salt = new byte[16].
The output finalCiphertext.toString() returns only class and hex hashcode of the object, s. here. For a meaningful output use a Base64 or hex encoding of the byte[] instead, e.g. Base64.getEncoder().encodeToString(finalCiphertext).
Related
I'm trying to encrypt and decrypt a string in java using AES/CBC/PKCS5Padding. It encrypts/decrypts without any compilation errors but the string after decryption is just random characters. this is my code:
Encryption:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(key.toCharArray(), "salt".getBytes(), 65536, 256);
SecretKey secret =new SecretKeySpec(factory.generateSecret(spec).getEncoded(),"AES");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivp = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivp);
byte[] encryptedText = cipher.doFinal(input.getBytes("UTF-8));
return Base64.getEncoder().encodeToString(encryptedText);
Decryption:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(key.toCharArray(), "salt".getBytes(), 65536, 256);
SecretKey secret =new SecretKeySpec(factory.generateSecret(spec).getEncoded(),"AES");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivp = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, ivp);
byte[] decryptedtext = cipher.doFinal(Base64.getDecoder().decode(input));
return new String(decryptedtext, "UTF-8");
The given text is the same every time but the result changes and is filled with random characters.
Thanks in advance!
I have some problem decrypting text with CryptoJS that has been encrypted with Java. The decryption should be done with AES/CBC/PKCS5Padding. The encrypted string is base64 encoded and I decode it before trying to decrypt the string.
This is how the Java code looks like:
private static byte[] doAES(int mode, byte[] dataToEncrypt, String secretKey, String salt) throws Exception {
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(mode, secretKeySpec, ivspec);
return cipher.doFinal(dataToEncrypt);
}
And this is how I attempt to decrypt it in CryptoJS. SharedSecretKey is both the secretKey and the salt value in Java.
let decoded = Buffer.from(encodedString, 'base64')
console.log("Result: " + CryptoJS.AES.decrypt({
ciphertext: CryptoJS.enc.Hex.parse(decoded.toString().substring(32))
}, CryptoJS.enc.Hex.parse(CryptoJS.SHA1(sharedSecretKey).toString().substring(0,32)),
{
iv: CryptoJS.lib.WordArray.create(Buffer.alloc(16)),
}).toString(CryptoJS.enc.Utf8))
Where decoded is the decoded Base64-string that I want to decrypt. However, this does not work and I get the error 'Error: Malformed UTF-8 data'. I am not sure if there is anything I have missed, any help is very much appreciated.
In the CryptoJS code the key derivation with PBKDF2 is missing. CryptoJS uses SHA1 for this by default.
The ciphertext can be passed Base64 encoded and will be implicitly converted to a CipherParams object:
var encodedString = "0O15lUg8sE1G0+BjO5N2j8AjVKXV4J+18z5DinbM6tYjoILhL0WDTFWbcTiN+pG/";
var key = CryptoJS.PBKDF2(
"my passphrase",
"my salt",
{
keySize: 256 / 32,
iterations: 65536
}
);
var decryptedData = CryptoJS.AES.decrypt(encodedString, key,
{
iv: CryptoJS.enc.Utf8.parse("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
}
);
console.log("Result: " + decryptedData.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
The ciphertext in the above example was previously generated with the posted Java code.
Note that a static IV is insecure. The IV is usually randomly generated for each encryption and passed along with the ciphertext (typically concatenated). Analogously for the salt, it is randomly generated for each key derivation and passed together with ciphertext and IV, e.g. salt|iv|ciphertext. Both, IV and salt, are not secret and need not be encrypted.
In addition, the encoding should be specified in the Java code when encoding with getBytes() and decoding with new String(), otherwise cross-platform problems may occur.
I need to wrap private value with AESWrap. I have a problem with this operation because of length of my private value string (key to be wrapped).
This is my implementation:
final byte[] kek = // ... generate SHA-256 key via `PBKDF2WithHmacSHA256`
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AESWrap", "SunJCE");
c.init(Cipher.WRAP_MODE, sKey);
byte[] bytes = privateValue.getBytes();
SecretKeySpec wk = new SecretKeySpec(bytes, "AES");
byte[] result = c.wrap(wk);
Since SunJCE provider doesn't support any padding for key wrapping so private value should be multiples of 8 bytes and this is my problem I need to solve.
Question: How to solve this situation when private value has not sufficient length. Is there some recommended way how to do padding on my own?
P.S. I would like to avoid external libraries like BC etc.
With respect to #Maarten's answer I created this implementation. It works (it wraps and unwraps my private value successfully), but is this implementation secure?
Wrapping
byte[] salt = .... // 32 random bytes...
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
c.init(Cipher.WRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
byte[] result = c.wrap(wk); // wrapped private value
Unwrapping
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and previous salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
IvParameterSpec iv = new IvParameterSpec(parsed.getIv()); // previously created iv
c.init(Cipher.UNWRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
Key result = c.unwrap(parsed.getKey(), "AES", Cipher.SECRET_KEY);
byte[] pv = result.getEncoded(); // unwrapped private value
It is possible to use a normal mode of operation rather than a specialized padding mode for AES. The padding mode is nicer, but just CBC with PKCS#7 padding should suffice as well.
It would be wise to use an IV and store it with the wrapped key. Private keys are generally not just binary data, but have structure, and you may leak a tiny bit of information if you wrap multiple keys this way. For RSA the randomized modulus comes before the private exponent / CRT parameters so you should be secure with a zero IV as well.
// --- key pair with private key for testing
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(4096);
KeyPair kp = gen.generateKeyPair();
// --- create KEK
final byte[] kek = new byte[16]; // test value
SecretKey sKey = new SecretKeySpec(kek, "AES");
// --- the cipher, not a special wrapping algorithm
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
// --- create IV
// not really necessary because the modulus comes first, but nicer
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// --- init & wrap by normal encryption
c.init(Cipher.WRAP_MODE, sKey, iv);
byte[] result = c.wrap(kp.getPrivate());
AES-SIV would be nicer, but that's not included in the SunJCE provider. You could use AES-GCM which ads integrity but beware that repeating the 12 byte IV (nonce) for that can be catastrophic for that mode.
I am trying to decrypt a saml response using AES and RSA, and I could decrypt the saml assertion properly. But, the decrypted text is being embedded in to some junk characters, which is causing parsing exceptions.
Below is my code
InputStream privateKeyFileInputStream = Check.class.getClassLoader().getResourceAsStream("rsa_privatekey.key");
rsaPrivateKey = new byte[privateKeyFileInputStream.available()];
privateKeyFileInputStream.read(rsaPrivateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
PrivateKey privKey = keyFactory.generatePrivate(privateKeySpec);
Cipher cipher1 = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding", "BC");
cipher1.init(Cipher.DECRYPT_MODE, privKey);
byte[] encryptedMessage = Base64.decodeBase64(aesPrivateKeyEnc.getBytes());
aesPrivateKey = cipher1.doFinal(encryptedMessage);
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec key = new SecretKeySpec(aesPrivateKey, "AES");
Cipher cipher2 = Cipher.getInstance("AES/CBC/NoPadding", "BC");
cipher2.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] cipherTextBytes = Base64.decodeBase64(cipherText);
byte[] decryptedMessage = cipher2.doFinal(cipherTextBytes);
String message = new String(decryptedMessage, "UTF8");
Now, the message has
R����=2�W���?<saml:Assertion ...... </saml:Assertion>��fE]����
It seems that your IV value is prefixed to the ciphertext. Instead of a zero IV you should use the first 16 bytes of your ciphertext for cipher2. Don't forget to exclude them from encryption. This explains the garbage at the start.
It also seems that your cipher2 should be configured for padding. This is probably PKCS#7 padding. Please try "AES/CBC/PKCS5Padding" instead of "/NoPadding". If that doesn't work you'll need to update your question with the plaintext in hexadecimals so we can determine which padding is used. That should explain the garbage at the end.
Note that "PKCS5Padding" does perform PKCS#7 padding in Java.
I'm in need of a simple AES cryptosystem in ECB. I have one working at the moment in the sense that given the same key twice in a row, it will correctly encrypt and decrypt a message.
However, if I use two different keys for encrypting/decrypting, the program throws a javax.crypto.BadPaddingException: Given final block not properly padded. I need the program to provide an incorrect decryption, presumably something that looks like some encrypted string. Here's my code:
public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] ciphertext = cipher.doFinal(plaintext);
return ciphertext;
}
public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] plaintext = cipher.doFinal(ciphertext);
return plaintext;
}
(Note: I'm aware of the disadvantages of using ECB, salt = "12345678", etc., but it's not my concern at the moment.) Thanks for any and all help.
PKCS#5 padding has a very specific structure, so you cannot continue using it if you want decryption with the wrong key to complete without error.
A good way to achieve your goal might be to use a stream mode of operation, rather than a block-mode. In a stream mode, the input key is used to produce a never-ending stream of seemingly random data, which is XORed with the ciphertext to produce plaintext (and vice versa). If you use the wrong key, you get nonsense data out which is the same size as the original plaintext.
Here's a simple example, based on your original code. I use an IV of all zeroes, but you may wish to improve that to be a random value in due course (note: you'll need to store this value with the ciphertext).
public static void main(String[] args) throws Exception {
byte[] plaintext = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
byte[] ciphertext = encrypt(plaintext, "foo");
byte[] goodDecryption = decrypt(ciphertext, "foo");
byte[] badDecryption = decrypt(ciphertext, "bar");
System.out.println(DatatypeConverter.printHexBinary(goodDecryption));
System.out.println(DatatypeConverter.printHexBinary(badDecryption));
}
public static SecretKey makeKey(String key) throws GeneralSecurityException {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
SecretKey secret = makeKey(key);
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
SecretKey secret = makeKey(key);
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
return cipher.doFinal(ciphertext);
}
Output:
00010203040506070809
5F524D4A8D977593D34C