I try to encrypt the text with accent and the node js server fails to decrypt it I get the response "Error: Unsupported state or unable to authenticate data". How can I do?
Here is Android file to encrypt text.
public static String encrypt(String text, String masterKey) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
byte[] iv = generateIv(16);
byte[] salt = generateIv(64);
SecretKey key = getKeyFromPassword(masterKey, salt);
byte[] cipher = encryptHelper("AES/GCM/NoPadding", text, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] ciphertext = Arrays.copyOfRange(cipher, 0, text.length());
byte[] tag = Arrays.copyOfRange(cipher, text.length(), cipher.length);
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(tag);
outputStream.write(ciphertext);
String base64Encrypted;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
base64Encrypted = Base64.getEncoder().encodeToString(outputStream.toByteArray());
}else{
base64Encrypted = android.util.Base64.encodeToString(outputStream.toByteArray(), android.util.Base64.DEFAULT);
}
return base64Encrypted;
}
public static byte[] encryptHelper(String algorithm, String input, SecretKey key,
GCMParameterSpec gcmParameterSpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
return cipher.doFinal(input.getBytes(StandardCharsets.UTF_8));
}
And here is node js file to decrypt text.
function decryptDataTestWork(encdata, masterkey) {
// body...
// base64 decoding
const bData = Buffer.from(encdata, 'base64');
// convert data to buffers
const salt = bData.slice(0, 64);
const iv = bData.slice(64, 80);
const tag = bData.slice(80, 96);
const text = bData.slice(96);
/*console.log(salt);
console.log(iv);
console.log(tag);
console.log(text);*/
// derive key using; 32 byte key length
const key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');
// AES 256 GCM Mode
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
// encrypt the given text
return decipher.update(text, 'binary', 'utf8') decipher.final('utf8');
};
I get this response
Error: Unsupported state or unable to authenticate data
Here is the text and key
"description":"Désolé"
"key":"tIJg9tyj2setkg7gknVreP5PXzRQV5J3"
How can i do please? Thanks!
CodePudding user response:
The problem is in the Java code and in the wrong separation of ciphertext and tag.
You determine the ciphertext size from the plaintext size (which is possible for GCM because of the absent padding), but then assume that the string text
has the same size as the byte array text.getBytes(StandardCharsets.UTF_8)
, which is generally not the case if the text characters are encoded on more than one byte.
The following implementation uses the tag size to separate both parts:
byte[] ciphertext = Arrays.copyOfRange(cipher, 0, cipher.length - GCM_TAG_LENGTH);
byte[] tag = Arrays.copyOfRange(cipher, cipher.length - GCM_TAG_LENGTH, cipher.length);
With this change, decryption with the NodeJS code is successful on my machine assuming the same key (note that key derivation cannot be checked because getKeyFromPassword()
is missing on the Java side).
CodePudding user response:
After the fix suggested the code works works very
public static String encrypt(String text, String masterKey) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
byte[] iv = generateIv(16);
byte[] salt = generateIv(64);
SecretKey key = getKeyFromPassword(masterKey, salt);
byte[] cipher = encryptHelper("AES/GCM/NoPadding", text, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] ciphertext = Arrays.copyOfRange(cipher, 0, cipher.length - GCM_TAG_LENGTH);
byte[] tag = Arrays.copyOfRange(cipher, cipher.length - GCM_TAG_LENGTH, cipher.length);
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(tag);
outputStream.write(ciphertext);
String base64Encrypted;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
base64Encrypted = Base64.getEncoder().encodeToString(outputStream.toByteArray());
}else{
base64Encrypted = android.util.Base64.encodeToString(outputStream.toByteArray(), android.util.Base64.DEFAULT);
}
return base64Encrypted;
}
public static SecretKey getKeyFromPassword(String masterKey, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(masterKey.toCharArray(), salt, 2145, 256);
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}
public static byte[] encryptHelper(String algorithm, String input, SecretKey key,
GCMParameterSpec gcmParameterSpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
return cipher.doFinal(input.getBytes(StandardCharsets.UTF_8));
}
public static byte[] generateIv(int N) {
byte[] iv = new byte[N];
new SecureRandom().nextBytes(iv);
return iv;
}
Thanks you @Topaco! You saved me!