【问题标题】:Translating Java AES encryption to nodejs, but 16 bytes missing after encryption将Java AES加密转换为nodejs,但加密后缺少16个字节
【发布时间】:2021-06-09 06:03:03
【问题描述】:

我现在正在编写一个nodejs服务器,并且需要从java服务器调用api,提供的加密代码是用java编写的,因此我需要将代码翻译成带有crypto的nodejs。

java中的代码如下

    public static String encrypt(byte[] contentByte, byte[] key) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        byte[] encrypted = null;
        try {
            SecureRandom secureRandom = new SecureRandom(); 
            byte[] iv = new byte[12];
            secureRandom.nextBytes(iv);
            Cipher cipher = Cipher.getInstance(AES_GCM_NOPADDING); 
            GCMParameterSpec parameterSpec = new GCMParameterSpec("tLen":128, iv); 
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, parameterSpec);
            encrypted = cipher.doFinal(contentByte); 

            System.out.println(encrypted.length);
            // the remaining code is not important so I stop here
        }
String content = "hello world";
String encrypt_string = encrypt(content.getBytes(),  decodedBytes); //the decodedbytes is the buffer of private key 

“hello world”打印的长度为 27

然后我用 nodejs 重写它

var crypto = require("crypto");

key = Buffer.from(key, "base64") 

let byte_data = `hello world`
byte_data = Buffer.from(byte_data, 'utf-8')
const iv = crypto.randomBytes(12)
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv)  //the key is not valid for non 256 algorithm
let crypt = cipher.update(byte_data,"", "base64");
crypt += cipher.final("base64");
crypt_byte = Buffer.from(crypt, "base64")
console.log(crypt_byte.length)

打印的长度是 11。

我想知道 nodejs 代码中缺少 GCMParameterSpec(128, iv) 是因为 128 位是 16 个字节,但是请参阅加密文档,在 crypto.createCipheriv("aes-256-gcm", key, iv) 中,它有一个使用 gcm 的默认身份验证标签长度为 16。还是我的代码有其他问题。

【问题讨论】:

  • GCM 在加密过程中自动生成一个认证标签。解密需要标签。在 Java 代码中,此标记隐式附加到密文中。在 NodeJS 代码中,这不会发生,而是必须使用cipher.getAuthTag()(目前缺少!)显式确定标记。由于 GCM 不填充,密文长度与明文长度相同,因此对于明文 hello world 和 16 字节的身份验证标签,Java 代码给出 11 + 16 = 27 字节的结果和NodeJS 编码一个 11 字节的结果。
  • @Topaco 但是如何将身份验证标签添加到nodejs中的加密缓冲区中,因为getAuthTag()必须在.final()之后运行,我不知道我应该如何以及在哪里添加它,我是一个漂亮的密码学外行
  • 我没有看到问题。使用 NodeJS 加密时,标签由cipher.getAuthTag() 确定。使用 NodeJS 解密时,设置为decipher.setAuthTag(buffer[, encoding])。用Java解密时,密文和标签必须事先按照ciphertext | tag的顺序拼接起来。

标签: java node.js cryptojs aes-gcm


【解决方案1】:

以下代码是在 Java 和 NodeJs 中使用“Crypto”进行 AES 256 GCM 加密的完整运行示例。

代码未针对密文处理进行优化,但明确显示了如何处理(随机)生成的 nonce(或 IV),在加密和解密方面带有和不带有 GCM 标签的密文。 p>

请注意,没有异常处理 - 只需将代码视为“教育”即可。

这是一个输出:

AES GCM 256 String encryption with random key
plaintext:  The quick brown fox jumps over the lazy dog
encryptionKey (Base64): /zTcqOfE8PdzrYmo6V9+YkWynSbDqqKo5VOrJzwDWME=

* * * Encryption * * *
ciphertext: qlzHXt+76TP8aoFM:C317D1/LQ1wGCOXRX/Z/9+hYR7XtcsEH3fGNOevGiia4/tVW6TlU9zXZ1A==:SCRyTSzXLo0B0TB2ycl5Sg==
output is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag

* * * Decryption * * *
decryptionKey (Base64): /zTcqOfE8PdzrYmo6V9+YkWynSbDqqKo5VOrJzwDWME=
ciphertext (Base64): qlzHXt+76TP8aoFM:C317D1/LQ1wGCOXRX/Z/9+hYR7XtcsEH3fGNOevGiia4/tVW6TlU9zXZ1A==:SCRyTSzXLo0B0TB2ycl5Sg==
input is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag
plaintext:  The quick brown fox jumps over the lazy dog

Java:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class AesGcm256StringEncryption {
    public static void main(String[] args) throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
        System.out.println("AES GCM 256 String encryption with random key");

        String plaintext = "The quick brown fox jumps over the lazy dog";
        System.out.println("plaintext:  " + plaintext);

        // generate random key
        byte[] encryptionKey = generateRandomAesKey();
        String encryptionKeyBase64 = base64Encoding(encryptionKey);
        System.out.println("encryptionKey (Base64): " + encryptionKeyBase64);

        // encryption
        System.out.println("\n* * * Encryption * * *");
        String ciphertextBase64 = aesGcmEncryptToBase64(encryptionKey, plaintext);
        System.out.println("ciphertext: " + ciphertextBase64);
        System.out.println("output is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag");

        // decryption
        System.out.println("\n* * * Decryption * * *");
        String decryptionKeyBase64 = encryptionKeyBase64; // full
        String ciphertextDecryptionBase64 = ciphertextBase64;
        System.out.println("decryptionKey (Base64): " + decryptionKeyBase64);
        byte[] decryptionKey = base64Decoding(decryptionKeyBase64);
        System.out.println("ciphertext (Base64): " + ciphertextDecryptionBase64);
        System.out.println("input is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag");
        String decryptedtext = aesGcmDecryptFromBase64(decryptionKey, ciphertextDecryptionBase64);
        System.out.println("plaintext:  " + decryptedtext);
    }

    private static byte[] generateRandomAesKey() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] key = new byte[32];
        secureRandom.nextBytes(key);
        return key;
    }

    private static byte[] generateRandomNonce() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] nonce = new byte[12];
        secureRandom.nextBytes(nonce);
        return nonce;
    }

    private static String aesGcmEncryptToBase64(byte[] key, String data) throws NoSuchPaddingException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        byte[] nonce = generateRandomNonce();
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonce);
        Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
        byte[] ciphertextWithTag = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        byte[] ciphertext = new byte[(ciphertextWithTag.length-16)];
        byte[] gcmTag = new byte[16];
        System.arraycopy(ciphertextWithTag, 0, ciphertext, 0, (ciphertextWithTag.length - 16));
        System.arraycopy(ciphertextWithTag, (ciphertextWithTag.length-16), gcmTag, 0, 16);
        String nonceBase64 = base64Encoding(nonce);
        String ciphertextBase64 = base64Encoding(ciphertext);
        String gcmTagBase64 = base64Encoding(gcmTag);
        return nonceBase64 + ":" + ciphertextBase64 + ":" + gcmTagBase64;
    }

    private static String aesGcmDecryptFromBase64(byte[] key, String data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        String[] parts = data.split(":", 0);
        byte[] nonce = base64Decoding(parts[0]);
        byte[] ciphertextWithoutTag = base64Decoding(parts[1]);
        byte[] gcmTag = base64Decoding(parts[2]);
        byte[] encryptedData = concatenateByteArrays(ciphertextWithoutTag, gcmTag);
        Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonce);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
        return new String(cipher.doFinal(encryptedData));
    }

    private static String base64Encoding(byte[] input) {
        return Base64.getEncoder().encodeToString(input);
    }
    private static byte[] base64Decoding(String input) {
        return Base64.getDecoder().decode(input);
    }

    public static byte[] concatenateByteArrays(byte[] a, byte[] b) {
        return ByteBuffer
                .allocate(a.length + b.length)
                .put(a).put(b)
                .array();
    }
}

NodeJs:

var crypto = require('crypto');

console.log('AES GCM 256 String encryption with random key full');

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

// generate random key
var encryptionKey = generateRandomAesKey();
var encryptionKeyBase64 = base64Encoding(encryptionKey);
console.log('encryptionKey (Base64): ', encryptionKeyBase64);

console.log('\n* * * Encryption * * *');

var ciphertextBase64 = aesGcmEncryptToBase64(encryptionKey, plaintext);
console.log('ciphertext (Base64): ' + ciphertextBase64);
console.log('output is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag');

console.log('\n* * * Decryption * * *');
var decryptionKeyBase64 = encryptionKeyBase64;
var ciphertextDecryptionBase64 = ciphertextBase64;
console.log('decryptionKey (Base64): ', decryptionKeyBase64);
console.log('ciphertext (Base64): ', ciphertextDecryptionBase64);
console.log('input is (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag');
var decryptedtext = aesGcmDecryptFromBase64(encryptionKey, ciphertextBase64);
console.log('plaintext: ', decryptedtext);

function aesGcmEncryptToBase64(key, data) {
  var nonce = generateRandomNonce();
  const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
  let encryptedBase64 = '';
  cipher.setEncoding('base64');
  cipher.on('data', (chunk) => encryptedBase64 += chunk);
  cipher.on('end', () => {
  // do nothing console.log(encryptedBase64);
  // Prints: some clear text data
  });
  cipher.write(data);
  cipher.end();
  var nonceBase64 = base64Encoding(nonce);
  var gcmTagBase64 = base64Encoding(cipher.getAuthTag());
  return nonceBase64 + ':' + encryptedBase64 + ':' + gcmTagBase64;
}

function aesGcmDecryptFromBase64(key, data) {
  var dataSplit = data.split(":");
  var nonce = base64Decoding(dataSplit[0]);
  var ciphertext = dataSplit[1];
  var gcmTag = base64Decoding(dataSplit[2]);
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(gcmTag);
  let decrypted = '';
  decipher.on('readable', () => {
    while (null !== (chunk = decipher.read())) {
      decrypted += chunk.toString('utf8');
    }
  });
  decipher.on('end', () => {
  // do nothing console.log(decrypted);
  });
  decipher.write(ciphertext, 'base64');
  decipher.end();
  return decrypted;
}  

function generateRandomAesKey() {
  return crypto.randomBytes(32);
}

function generateRandomNonce() {
  return crypto.randomBytes(12);
}

function base64Encoding(input) {
  return input.toString('base64');
}

function base64Decoding(input) {
  return Buffer.from(input, 'base64')
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 1970-01-01
    • 2011-09-23
    相关资源
    最近更新 更多