以下代码是在 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')
}