如何在 android 中使用 aes-256-gcm 加密带重音的文本
how to encrypt text with accent using aes-256-gcm in android
我尝试用重音加密文本,但节点 js 服务器无法解密我收到响应“错误:不支持的状态或无法验证数据”。我该怎么办?
这里是 Android 用于加密文本的文件。
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));
}
这里是解密文本的节点js文件。
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');
};
我收到了这个回复
Error: Unsupported state or unable to authenticate data
这是文本和密钥
"description":"Désolé"
"key":"tIJg9tyj2setkg7gknVreP5PXzRQV5J3"
请问我该怎么办?
谢谢!
问题出在Java代码和密文与标签的错误分隔上。
您根据明文大小确定密文大小(由于缺少填充,这对于 GCM 是可能的),但随后假设字符串 text
与字节数组 text.getBytes(StandardCharsets.UTF_8)
],如果文本字符的编码超过一个字节,则通常不会出现这种情况。
以下实现使用标记大小来分隔两个部分:
byte[] ciphertext = Arrays.copyOfRange(cipher, 0, cipher.length - GCM_TAG_LENGTH);
byte[] tag = Arrays.copyOfRange(cipher, cipher.length - GCM_TAG_LENGTH, cipher.length);
通过此更改,假定使用相同的密钥,在我的机器上使用 NodeJS 代码解密是成功的(请注意,无法检查密钥派生,因为 Java 端缺少 getKeyFromPassword()
)。
修复后建议代码工作正常
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;
}
谢谢@Topaco!你救了我!
我尝试用重音加密文本,但节点 js 服务器无法解密我收到响应“错误:不支持的状态或无法验证数据”。我该怎么办?
这里是 Android 用于加密文本的文件。
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));
}
这里是解密文本的节点js文件。
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');
};
我收到了这个回复
Error: Unsupported state or unable to authenticate data
这是文本和密钥
"description":"Désolé"
"key":"tIJg9tyj2setkg7gknVreP5PXzRQV5J3"
请问我该怎么办? 谢谢!
问题出在Java代码和密文与标签的错误分隔上。
您根据明文大小确定密文大小(由于缺少填充,这对于 GCM 是可能的),但随后假设字符串 text
与字节数组 text.getBytes(StandardCharsets.UTF_8)
],如果文本字符的编码超过一个字节,则通常不会出现这种情况。
以下实现使用标记大小来分隔两个部分:
byte[] ciphertext = Arrays.copyOfRange(cipher, 0, cipher.length - GCM_TAG_LENGTH);
byte[] tag = Arrays.copyOfRange(cipher, cipher.length - GCM_TAG_LENGTH, cipher.length);
通过此更改,假定使用相同的密钥,在我的机器上使用 NodeJS 代码解密是成功的(请注意,无法检查密钥派生,因为 Java 端缺少 getKeyFromPassword()
)。
修复后建议代码工作正常
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;
}
谢谢@Topaco!你救了我!