AES / GCM / NoPadding шифрует с узла и расшифровывает на java, выдает исключение AEADBadTagException: несоответствие тегов

#java #node.js #aes-gcm

#java #node.js #aes-gcm

Вопрос:

Я хочу зашифровать данные с помощью NodeJS, а затем расшифровать данные с помощью Java. Я искал много примеров в Интернете, но не нашел подходящего ответа. Код NodeJS выглядит следующим образом:

 const crypto = require('crypto');

function encrypt(content) {
    const salt = crypto.randomBytes(cryptoConfig.saltLength);
    const iv = crypto.randomBytes(cryptoConfig.ivLength);
    const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations, 
    cryptoConfig.keyLength, cryptoConfig.digest);
    const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);

    const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);

    const tag = cipher.getAuthTag();

    const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
    return encdata;
}

const cryptoConfig = {
    cipherAlgorithm: 'aes-256-gcm',
    masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
    iterations: 65535,
    keyLength: 32,
    saltLength: 16,
    ivLength: 12,
    tagLength: 16,
    digest: 'sha512'
}
 

const EncryptedData = encrypt(‘привет, мир’);
console.log(‘encrypt data ->’, EncryptedData);

Код JAVA:

 private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";

private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
private static final int KEY_LENGTH = 16;
private static final int ITERATIONS = 65535;

public static String decrypt(String cipherContent, String password) throws Exception {
    byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
    ByteBuffer bb = ByteBuffer.wrap(decode);
    byte[] salt = new byte[SALT_LENGTH];
    bb.get(salt);
    byte[] iv = new byte[IV_LENGTH];
    bb.get(iv);
    byte[] content = new byte[bb.remaining()];
    bb.get(content);

    Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
    SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
    cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));

    byte[] plainText = cipher.doFinal(content);
    return new String(plainText, UTF_8);
}

private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    return secret;
}

public static void main(String[] args) throws Exception {
    String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
    String encryptedData = "<NodeJS return encryptedData>";
    String decryptedText = decrypt(encryptedData, masterKey);
    System.out.println("Decrypted: "   decryptedText));
}
 

После выполнения генерирует исключение:

 Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
    at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    at com.xxx.common.AesCrypto.decrypt(AesCrypto.java:92)
    at com.xxx.common.AesCrypto.main(AesCrypto.java:131)
 

как я могу измениться, чтобы эта сцена прошла через

Ответ №1:

В ваших кодах всего 2 проблемы, которые мешают вам успешно шифровать между NodeJS / Crypto и Java, но с 2 измененными строками кода все будет работать так, как ожидалось.

NodeJS: в конце вашей функции шифрования вы объединяете параметры, необходимые для расшифровки, в больший массив. Поскольку Java ожидает ciphertext:gcmTag ввода, вы должны изменить строку

 const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
// ### put the auth tag at the end of encrypted
to
const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
 

Java: вы запрашиваете шифрование AES GCM 256, и это означает, что вам нужно использовать ключ длиной 256/8 = 32 байта с обеих сторон,
поэтому просто измените константу

 private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
to:
private static final int KEY_LENGTH = 32;
 

Вы можете запустить оба кода онлайн здесь, NodeJS: https://repl.it/@javacrypto/SoNodeJsCryptoAesGcm256Pbkdf2StringEncryption#index.js и Java:
https://repl.it/@javacrypto/SoJavaAesGcm256Pbkdf2StringDecryption#Main.java

Вот результаты:

NodeJS:

 AES GCMC 256 String encryption with PBKDF2 derived key
plaintext:   The quick brown fox jumps over the lazy dog
ciphertext:  InFN0Zz5MGLTOr5B0xFnvU1k2dPPZPSen7pCWiQguCEQe6KSBSAQjHtUvIeuA7jcl8fK7L63xbq tsHe9HRYupp554tbEESjrCbsCh1lW2exWDp4YLQd
 

Java:

 AES GCMC 256 String decryption with PBKDF2 derived key
Decrypted: The quick brown fox jumps over the lazy dog
 

Предупреждение безопасности: коды не обрабатывают исключения и предназначены только для образовательных целей.

NodeJS:

 var crypto = require('crypto');

console.log('AES GCMC 256 String encryption with PBKDF2 derived key');

var plaintext = 'The quick brown fox jumps over the lazy dog';
console.log('plaintext:  ', plaintext);

const cryptoConfig = {
    cipherAlgorithm: 'aes-256-gcm',
    masterKey: 'sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=',
    iterations: 65535,
    keyLength: 32,
    saltLength: 16,
    ivLength: 12,
    tagLength: 16,
    digest: 'sha512'
}

var ciphertext = encrypt(plaintext);
console.log('ciphertext: ', ciphertext);

function encrypt(content) {
    const salt = crypto.randomBytes(cryptoConfig.saltLength);
    const iv = crypto.randomBytes(cryptoConfig.ivLength);
    const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
    cryptoConfig.keyLength, cryptoConfig.digest);
    const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
    const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    // ### put the auth tag at the end of encrypted
    //const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
    const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
    return encdata;
}
 

Java:

 import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

public class Main {

    private static final Charset UTF_8 = StandardCharsets.UTF_8;
    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH = 16;
    private static final int IV_LENGTH = 12;
    private static final int SALT_LENGTH = 16;
    //private static final int KEY_LENGTH = 16; // ### key length for aes gcm 256 is 32 byte
    private static final int KEY_LENGTH = 32;
    private static final int ITERATIONS = 65535;

    public static void main(String[] args) throws Exception {
        System.out.println("AES GCMC 256 String decryption with PBKDF2 derived key");

        String masterKey = "sfcpnnjFG6dULJfo1BEGqczpfN0SmwZ6bgKO5FcDRfI=";
        String encryptedData = "YPp3VDo8NeNoXONTHyJ2oy7nSHbFNK31CbA8YU5KBW3ybJeOt 0rLiYQq4y62WknGgYnjom9hT1fqBnXZphZAy5EoOOXh/U1wcJSSO4cQ086GJf6mXCa";
        String decryptedText = decrypt(encryptedData, masterKey);
        System.out.println("Decrypted: "   decryptedText);
    }

    public static String decrypt(String cipherContent, String password) throws Exception {
        byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
        ByteBuffer bb = ByteBuffer.wrap(decode);
        byte[] salt = new byte[SALT_LENGTH];
        bb.get(salt);
        byte[] iv = new byte[IV_LENGTH];
        bb.get(iv);
        byte[] content = new byte[bb.remaining()];
        bb.get(content);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
        cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
        byte[] plainText = cipher.doFinal(content);
        return new String(plainText, UTF_8);
    }

    private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
        return secret;
    }
}