So encryption is being done in Java and decryption was being done in Java but now we have a new service to do it in Javascript. For 90% of the datakeys, it works, but there is one case it hasn’t been working. I have provided the steps below to see if anyone can see what is different or incorrect about the Javascript implementation.
The implementation uses a plaintext datakey that is decrypted using kms. That decryptedDataKey is then used to create a PBEKeySpec which is used to decrypt the string. The failing decrypt ends with the error: Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Javascript:
import { KMS } from 'aws-sdk';
const kms = new KMS({ region: 'us-east-1' });
async function decryptDataKey(encryptedDataKey) {
const cipherTextBlob = Buffer.from(encryptedDataKey, 'base64');
const decryptRequest = {
CiphertextBlob: cipherTextBlob
};
const data = await kms.decrypt(decryptRequest).promise();
return data.Plaintext.toString('utf8');
}
import * as crypto from 'crypto';
export async function generateSecret(key: string, salt: string): Promise<Buffer> {
const complexity = 128 / 8;
return new Promise((resolve, reject) => {
try {
crypto.pbkdf2(key, salt, 65536, complexity, 'sha1', (e, s) => {
if (e) {
reject(e);
} else {
resolve(s);
}
});
} catch (ex) {
reject(ex);
}
});
}
it('should work', async () => {
const dataKey = 'keyInPlainText';
const salt = 'salt';
const encryptedString = 'encryptedString';
const expectedValue = 'expectedValue'
const decryptedDatakey = await decryptDataKey(dataKey)
const secretKeySpec = await generateSecret(decryptedDatakey, salt);
const res = await decrypt(encryptedString, secretKeySpec, 'AES-128-ECB', 'utf8');
expect(res).to.equal(expectedValue);
});
Java code:
@Override
public String decryptDataKey(String encryptedDataKey) {
byte[] cipherTextBlob = Base64.getDecoder().decode(encryptedDataKey);
DecryptRequest decryptRequest =
DecryptRequest.builder().ciphertextBlob(SdkBytes.fromByteArray(cipherTextBlob)).build();
byte[] decryptedDataKey =
ByteBuffer.wrap(kmsClient.decrypt(decryptRequest).plaintext().asByteArray()).array();
return new String(decryptedDataKey, StandardCharsets.UTF_8);
}
}
public SecretKeySpec createSecretKeySpec(String dataKey, String saltData) throws CryptoException {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec =
new PBEKeySpec(
dataKey.toCharArray(),
saltData.getBytes("UTF-8"),
65536,
128);
SecretKey secretKey = factory.generateSecret(pbeKeySpec);
byte[] b = secretKey.getEncoded();
return new SecretKeySpec(secretKey.getEncoded(), "AES");
} catch (InvalidKeySpecException | NoSuchAlgorithmException | UnsupportedEncodingException ex) {
throw new CryptoException(ex.getMessage());
}
}
public void testDecryptSingleEncryptedStringSuccessfully()
throws CryptoException {
String dataKey = "keyInPlainText";
String salt = "salt";
String encryptedString = "encryptedString";
String expectedValue = "expectedValue";
String decryptedDataKey = dataKeyDecryption.decryptDataKey(dataKey);
SecretKeySpec secretKeySpec = ingestionCrypto.createSecretKeySpec(decryptedDataKey, salt);
String data = ingestionCrypto.decrypt(encryptedField, secretKeySpec);
assertNotNull(data);
assertEquals(expectedDecryptedData, data);
}
Please note the values I have used for the test case are not real and will not work. I do not have a working in this case since I cannot provide the actual keys we use.
I have not provided the decrypt function because I know that is not the culprit. If I convert the Java output in bytes to a Buffer in javascript and use that as that as the secretKeySpec, then decryption works. I think the failure is on decrypting the dataKey cause on the working test, the output matches the Java string but it doesn’t on the non-working test (I only thing that jumps out to me is there is a newline on the failing decrypted string, but they both have it). Also if take the decryptedString output from Java and hardcode that in the javascript code, it works for the working example but not for the non-working example (so not sure if there is some wonkiness with the strings at play).