AES-GCM在NODEJS和JAVA加解密的坑

最終成果是AES-128-GCM,先上代碼

NODEJS

import crypto from 'crypto' //crypto是nodejs內置模塊

const iv = "0123456789ABCDEF";

//加密方法

function encodeAes(word, aesKey) {

if (!word) {

return ''

}

if (typeof word != 'string') {

word = JSON.stringify(word)

}

const md5 = crypto.createHash('md5');

const result = md5.update(aesKey).digest();

const cipher = crypto.createCipheriv('aes-128-gcm', result, iv);

const encrypted = cipher.update(word, 'utf8');

const finalstr = cipher.final();

const tag = cipher.getAuthTag();

const res = Buffer.concat([encrypted, finalstr, tag]);

return res.toString('base64');

}

//解密方法

function decodeAes(word, aesKey) {

if (!word) {

return ''

}

const md5 = crypto.createHash('md5');

const result = md5.update(aesKey).digest();

const decipher = crypto.createDecipheriv('aes-128-gcm', result, iv);

const b = Buffer.from(word, 'base64')

decipher.setAuthTag(b.subarray(b.length - 16));

const str = decipher.update(Buffer.from(b.subarray(0, b.length - 16), 'hex'));

const fin = decipher.final();

const decryptedStr = new TextDecoder('utf8').decode(Buffer.concat([str, fin]))

try {

return JSON.parse(decryptedStr);

} catch (e) {

return decryptedStr

}

}

export default {

encodeParams(origin, aesKey) {

if (!origin) {

origin = {};

}

encodeAes(origin, aesKey);

},

decodeParams(parameters, aesKey) {

if (!parameters) {

return "";

}

return decodeAes(parameters, aesKey);

}

}

JAVA

import java.security.MessageDigest;

import java.security.Security;

import java.util.Base64;

import javax.crypto.Cipher;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**

* @author Lisa

*/

public class GCMUtil {

private static final String IV = "0123456789ABCDEF";

private static final String ALGORITHMSTR = "AES/GCM/NoPadding";

private static final String DEFAULT_CODING = "utf-8";

/**

* 如果報錯java.security.NoSuchProviderException: no such provider: BC,那麼需要加上這一段,同時需要bcprov-jdk15on.jar

*/

static {

Security.addProvider(new BouncyCastleProvider());

}

/**

* 加密

* @param content

* @param encryptKey

* @param iv

* @return

* @throws Exception

*/

public static String aesEncrypt(String content, String encryptKey) throws Exception {

byte[] input = content.getBytes(DEFAULT_CODING);

MessageDigest md = MessageDigest.getInstance("MD5");

byte[] thedigest = md.digest(encryptKey.getBytes(DEFAULT_CODING));

SecretKeySpec skc = new SecretKeySpec(thedigest, "AES");

IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes(DEFAULT_CODING));

Cipher cipher = Cipher.getInstance(ALGORITHMSTR, "BC");

cipher.init(Cipher.ENCRYPT_MODE, skc, ivspec);

byte[] cipherText = new byte[cipher.getOutputSize(input.length)];

int ctLength = cipher.update(input, 0, input.length, cipherText, 0);

ctLength += cipher.doFinal(cipherText, ctLength);

return Base64.getEncoder().encodeToString(cipherText);

}

/**

* 解密

* @param tmp

* @param decryptKey

* @param iv

* @return

* @throws Exception

*/

public static String aesDecrypt(String tmp, String decryptKey) throws Exception {

byte[] keyb = decryptKey.getBytes(DEFAULT_CODING);

MessageDigest md = MessageDigest.getInstance("MD5");

byte[] thedigest = md.digest(keyb);

SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");

IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes(DEFAULT_CODING));

Cipher dcipher = Cipher.getInstance(ALGORITHMSTR, "BC");

dcipher.init(Cipher.DECRYPT_MODE, skey, ivspec);

byte[] clearbyte = dcipher.doFinal(Base64.getDecoder().decode(tmp));

return new String(clearbyte, DEFAULT_CODING);

}

public static void main(String[] args) throws Exception {

final String key = "alckdirtjgfl0tig";

String origin = "abcdefggghhhiiiijjjjjj中文測試";

String encryptstr = aesEncrypt(origin, key);

System.out.println(encryptstr);

String decryptstr= aesDecrypt(encryptstr, key);

System.out.println(decryptstr);

}

}

然後說說坑

1、JAVA256位祕鑰需要JDK支持,據說JDK1.8某個小版本之後是支持的,所以不是所有JDK8都支持

2、IV生成時,可以給字符串,也可以給長度。如果給長度,根據代碼不同,可能是長度個數的0(比如12個0),也可能是長度個數的隨機數。後者需要將IV和加密字符串一起傳遞。但是如果你不清楚代碼究竟用的哪種,那建議還是給定字符串吧。

3、NODEJS對於authTag的值並沒有要求一定要拼接到加密結果(甚至解密的時候是要去掉的),但是JAVA是把authTag的值自行拼接到加密結果裏面的。網上找的代碼,是分離加密結果前16位作爲authTag。但是我最後搞定是加密結果的最後16位……醉了。

4、NODEJS的hex,base64,binary轉換我反正是暈了,現在這個是排列組合後成功的結果,別問我爲什麼是這樣……JAVA就只要一個編碼,真好。

5、這個代碼裏面沒有加SALT鹽值,有興趣的可以自行探索。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章