没有身份验证标签的 AES GCM 解密
AES GCM decryption without authentication tag
我有一个问题。以我有限的密码学经验。
如何在没有身份验证标签的情况下解密 AES-128-GCM。
我找到了
好像解决了一些问题。但只有 iv 等于 96 位才能工作。
shadowsocksr OpenSSL support aes-128-gcm.不同于其他aes-128-gcm加密或解密使用认证标签
我写了一个测试脚本,得到了结果。
脚本:
from shadowsocks import encrypt
import binascii
ivLength = 16
key, iv = encrypt.EVP_BytesToKey(b"killer",16,12,None)
print("key: ",binascii.hexlify(key))
print("iv: ",binascii.hexlify(iv))
cipher = encrypt.Encryptor("killer","aes-128-gcm",iv=iv)
hello = cipher.encrypt(bytes("hello","utf-8"))
dehello = cipher.decrypt(hello)
print("origin: " ,binascii.hexlify(b'hello'))
print("Ciphertext: ",binascii.hexlify(hello))
print("Cleartext: ",binascii.hexlify(dehello))
当ivLength设置为12时:
key: b'b36d331451a61eb2d76860e00c347396'
iv: b'271d7f17d03ed7cd1f443274'
origin: b'68656c6c6f'
Ciphertext: b'e0fc2227c40bc9ea9343a1faafa4e23da750a9ad00'
Cleartext: b'68656c6c6f'
当 ivLength 设置为 16 时:
key: b'b36d331451a61eb2d76860e00c347396'
iv: b'271d7f17d03ed7cd1f44327456aebfa2'
origin: b'68656c6c6f'
Ciphertext: b'271d7f17d03ed7cd1f44327456aebfa215988b0365'
Cleartext: b'68656c6c6f'
我想用Java解密。如方法 所述。
我写了一个Java代码来解密上面的结果:
环境:
os环境:window10
jdk版本:JDK11
依赖性:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
代码:
package server;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Test;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class Whosebug {
@Test
public void AES_128_GCM_DECRYPT_TEST() throws IOException, NoSuchAlgorithmException, InvocationTargetException, NoSuchMethodException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, IllegalAccessException, DecoderException, ShortBufferException, IllegalBlockSizeException, ClassNotFoundException {
String ciphertext_with_iv_12 = "e0fc2227c40bc9ea9343a1faafa4e23da750a9ad00";
String ciphertext_with_iv_16 = "a54a0301968953c0b45288f4d78c4011800f974c2d";
System.out.println("--------------- descript with iv 12 ---------------");
AES_128_GCM_DECRYPT(ciphertext_with_iv_12, "killer", 16, 12);
System.out.println("--------------- descript with iv 16 ---------------");
AES_128_GCM_DECRYPT(ciphertext_with_iv_12, "killer", 16, 16);
}
public static void AES_128_GCM_DECRYPT(String ciphertext,String password,int keyLength,int ivLength) throws DecoderException, NoSuchPaddingException, NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
byte[] cipherdata = Hex.decodeHex(ciphertext);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
Object[] item = EVP_BytesToKey(password, keyLength, ivLength);
byte[] key = (byte[]) item[0];
byte[] iv = ArrayUtils.subarray(cipherdata, 0, ivLength);
//byte[] counter = Bytes.concat(iv, new byte[4]);
byte[] counter = getJ0Proxy(iv, key);
inc(counter);
final IvParameterSpec gcmParameterSpec = new IvParameterSpec(counter);
final SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
byte[] data = ArrayUtils.subarray(cipherdata, 16, cipherdata.length);
byte[] result = cipher.doFinal(data, 0, data.length);
System.out.println(String.format("key: %s", Hex.encodeHexString(key)));
System.out.println(String.format("iv: %s", Hex.encodeHexString(iv)));
System.out.println(String.format("origin: %s", Hex.encodeHexString("hello".getBytes())));
System.out.println(String.format("Ciphertext: %s", Hex.encodeHexString(data)));
System.out.println(String.format("Cleartext: %s", Hex.encodeHexString(result)));
}
/**
* if iv is not 96 bit,it need more complex approach to generate iv
*
* @param key
* @return
*/
public static final byte[] getSubKey(byte[] key) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Class<?> aesCrypt = Class.forName("com.sun.crypto.provider.AESCrypt");
Constructor constructor = aesCrypt.getDeclaredConstructor();
constructor.setAccessible(true);
Object aesCryptInstance = constructor.newInstance();
Method aesInstanceInit = aesCrypt.getDeclaredMethod("init", boolean.class, String.class, byte[].class);
aesInstanceInit.setAccessible(true);
aesInstanceInit.invoke(aesCryptInstance, false, "AES", key);
byte[] subKey = new byte[16];
Method aesInstanceEncryptBlock = aesCrypt.getDeclaredMethod("encryptBlock", byte[].class, int.class, byte[].class, int.class);
aesInstanceEncryptBlock.setAccessible(true);
aesInstanceEncryptBlock.invoke(aesCryptInstance, new byte[16], 0, subKey, 0);
return subKey;
}
/**
* generator iv for CTR use.
*
* @param iv
* @param subkey
* @return
*/
public static final byte[] getJ0Proxy(byte[] iv, byte[] subkey) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Method getJ0 = galoisCounterMode.getDeclaredMethod("getJ0", byte[].class, byte[].class);
getJ0.setAccessible(true);
return (byte[]) getJ0.invoke(null, iv, subkey);
}
/**
* invok jdk com.sun.crypto.provider.GaloisCounterMode static method increment32
*
* @param counter iv
*/
private static final void inc(byte[] counter) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Method getJ0 = galoisCounterMode.getDeclaredMethod("increment32", byte[].class);
getJ0.setAccessible(true);
getJ0.invoke(null, counter);
}
/**
* for generator key and iv
*
* @param password password
* @param keyLength keyLength
* @param ivLength ivLength
* @return array have 2 element the index of 0 is key and index of 1 is iv.
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public static Object[] EVP_BytesToKey(String password, int keyLength, int ivLength) throws NoSuchAlgorithmException, UnsupportedEncodingException {
ArrayList<byte[]> m = new ArrayList();
int i = 0;
while (getByteArray(m) < keyLength + ivLength) {
String data = password;
if (i > 0) {
data = new String(m.get(i - 1), "ISO-8859-1") + password;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] dataDigest = digest.digest(data.getBytes("ISO-8859-1"));
m.add(dataDigest);
i += 1;
}
byte[] ms = joinByteArray(m);
byte[] key = ArrayUtils.subarray(ms, 0, keyLength);
byte[] iv = ArrayUtils.subarray(ms, keyLength, keyLength + ivLength);
return new Object[]{key, iv};
}
public static int getByteArray(List<byte[]> array) {
int length = 0;
for (byte[] item : array) {
length += item.length;
}
return length;
}
public static byte[] joinByteArray(List<byte[]> array) {
int length = getByteArray(array);
byte[] result = new byte[length];
int pos = 0;
for (byte[] item : array) {
System.arraycopy(item, 0, result, pos, item.length);
pos += item.length;
}
return result;
}
}
结果:
--------------- descript with iv 12 ---------------
key: b36d331451a61eb2d76860e00c347396
iv: e0fc2227c40bc9ea9343a1fa
origin: 68656c6c6f
Ciphertext: a750a9ad00
Cleartext: 68656c6c6f
--------------- descript with iv 16 ---------------
key: b36d331451a61eb2d76860e00c347396
iv: e0fc2227c40bc9ea9343a1faafa4e23d
origin: 68656c6c6f
Ciphertext: a750a9ad00
Cleartext: dead0c78cf
当iv的长度不是96bit时,结果是错误的
我希望有人能告诉我哪里出了问题。
提前致谢。
这是预期的结果。如果您查看 Nist 800-38D,第 15 页,您会看到:
if len(IV)=96 then
Y0=IV||0^31||1
else
Y0=GHASH(H,{},IV).
因此,较大的 IV 不会被修剪,因此建议进行其他操作。这样,您的 IV 可以具有更高的熵。
我有一个问题。以我有限的密码学经验。
如何在没有身份验证标签的情况下解密 AES-128-GCM。
我找到了
shadowsocksr OpenSSL support aes-128-gcm.不同于其他aes-128-gcm加密或解密使用认证标签
我写了一个测试脚本,得到了结果。
脚本:
from shadowsocks import encrypt
import binascii
ivLength = 16
key, iv = encrypt.EVP_BytesToKey(b"killer",16,12,None)
print("key: ",binascii.hexlify(key))
print("iv: ",binascii.hexlify(iv))
cipher = encrypt.Encryptor("killer","aes-128-gcm",iv=iv)
hello = cipher.encrypt(bytes("hello","utf-8"))
dehello = cipher.decrypt(hello)
print("origin: " ,binascii.hexlify(b'hello'))
print("Ciphertext: ",binascii.hexlify(hello))
print("Cleartext: ",binascii.hexlify(dehello))
当ivLength设置为12时:
key: b'b36d331451a61eb2d76860e00c347396'
iv: b'271d7f17d03ed7cd1f443274'
origin: b'68656c6c6f'
Ciphertext: b'e0fc2227c40bc9ea9343a1faafa4e23da750a9ad00'
Cleartext: b'68656c6c6f'
当 ivLength 设置为 16 时:
key: b'b36d331451a61eb2d76860e00c347396'
iv: b'271d7f17d03ed7cd1f44327456aebfa2'
origin: b'68656c6c6f'
Ciphertext: b'271d7f17d03ed7cd1f44327456aebfa215988b0365'
Cleartext: b'68656c6c6f'
我想用Java解密。如方法
环境:
os环境:window10
jdk版本:JDK11
依赖性:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
代码:
package server;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Test;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class Whosebug {
@Test
public void AES_128_GCM_DECRYPT_TEST() throws IOException, NoSuchAlgorithmException, InvocationTargetException, NoSuchMethodException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, IllegalAccessException, DecoderException, ShortBufferException, IllegalBlockSizeException, ClassNotFoundException {
String ciphertext_with_iv_12 = "e0fc2227c40bc9ea9343a1faafa4e23da750a9ad00";
String ciphertext_with_iv_16 = "a54a0301968953c0b45288f4d78c4011800f974c2d";
System.out.println("--------------- descript with iv 12 ---------------");
AES_128_GCM_DECRYPT(ciphertext_with_iv_12, "killer", 16, 12);
System.out.println("--------------- descript with iv 16 ---------------");
AES_128_GCM_DECRYPT(ciphertext_with_iv_12, "killer", 16, 16);
}
public static void AES_128_GCM_DECRYPT(String ciphertext,String password,int keyLength,int ivLength) throws DecoderException, NoSuchPaddingException, NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, BadPaddingException, IllegalBlockSizeException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
byte[] cipherdata = Hex.decodeHex(ciphertext);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
Object[] item = EVP_BytesToKey(password, keyLength, ivLength);
byte[] key = (byte[]) item[0];
byte[] iv = ArrayUtils.subarray(cipherdata, 0, ivLength);
//byte[] counter = Bytes.concat(iv, new byte[4]);
byte[] counter = getJ0Proxy(iv, key);
inc(counter);
final IvParameterSpec gcmParameterSpec = new IvParameterSpec(counter);
final SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
byte[] data = ArrayUtils.subarray(cipherdata, 16, cipherdata.length);
byte[] result = cipher.doFinal(data, 0, data.length);
System.out.println(String.format("key: %s", Hex.encodeHexString(key)));
System.out.println(String.format("iv: %s", Hex.encodeHexString(iv)));
System.out.println(String.format("origin: %s", Hex.encodeHexString("hello".getBytes())));
System.out.println(String.format("Ciphertext: %s", Hex.encodeHexString(data)));
System.out.println(String.format("Cleartext: %s", Hex.encodeHexString(result)));
}
/**
* if iv is not 96 bit,it need more complex approach to generate iv
*
* @param key
* @return
*/
public static final byte[] getSubKey(byte[] key) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Class<?> aesCrypt = Class.forName("com.sun.crypto.provider.AESCrypt");
Constructor constructor = aesCrypt.getDeclaredConstructor();
constructor.setAccessible(true);
Object aesCryptInstance = constructor.newInstance();
Method aesInstanceInit = aesCrypt.getDeclaredMethod("init", boolean.class, String.class, byte[].class);
aesInstanceInit.setAccessible(true);
aesInstanceInit.invoke(aesCryptInstance, false, "AES", key);
byte[] subKey = new byte[16];
Method aesInstanceEncryptBlock = aesCrypt.getDeclaredMethod("encryptBlock", byte[].class, int.class, byte[].class, int.class);
aesInstanceEncryptBlock.setAccessible(true);
aesInstanceEncryptBlock.invoke(aesCryptInstance, new byte[16], 0, subKey, 0);
return subKey;
}
/**
* generator iv for CTR use.
*
* @param iv
* @param subkey
* @return
*/
public static final byte[] getJ0Proxy(byte[] iv, byte[] subkey) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Method getJ0 = galoisCounterMode.getDeclaredMethod("getJ0", byte[].class, byte[].class);
getJ0.setAccessible(true);
return (byte[]) getJ0.invoke(null, iv, subkey);
}
/**
* invok jdk com.sun.crypto.provider.GaloisCounterMode static method increment32
*
* @param counter iv
*/
private static final void inc(byte[] counter) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
Class<?> galoisCounterMode = Class.forName("com.sun.crypto.provider.GaloisCounterMode");
Method getJ0 = galoisCounterMode.getDeclaredMethod("increment32", byte[].class);
getJ0.setAccessible(true);
getJ0.invoke(null, counter);
}
/**
* for generator key and iv
*
* @param password password
* @param keyLength keyLength
* @param ivLength ivLength
* @return array have 2 element the index of 0 is key and index of 1 is iv.
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public static Object[] EVP_BytesToKey(String password, int keyLength, int ivLength) throws NoSuchAlgorithmException, UnsupportedEncodingException {
ArrayList<byte[]> m = new ArrayList();
int i = 0;
while (getByteArray(m) < keyLength + ivLength) {
String data = password;
if (i > 0) {
data = new String(m.get(i - 1), "ISO-8859-1") + password;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] dataDigest = digest.digest(data.getBytes("ISO-8859-1"));
m.add(dataDigest);
i += 1;
}
byte[] ms = joinByteArray(m);
byte[] key = ArrayUtils.subarray(ms, 0, keyLength);
byte[] iv = ArrayUtils.subarray(ms, keyLength, keyLength + ivLength);
return new Object[]{key, iv};
}
public static int getByteArray(List<byte[]> array) {
int length = 0;
for (byte[] item : array) {
length += item.length;
}
return length;
}
public static byte[] joinByteArray(List<byte[]> array) {
int length = getByteArray(array);
byte[] result = new byte[length];
int pos = 0;
for (byte[] item : array) {
System.arraycopy(item, 0, result, pos, item.length);
pos += item.length;
}
return result;
}
}
结果:
--------------- descript with iv 12 ---------------
key: b36d331451a61eb2d76860e00c347396
iv: e0fc2227c40bc9ea9343a1fa
origin: 68656c6c6f
Ciphertext: a750a9ad00
Cleartext: 68656c6c6f
--------------- descript with iv 16 ---------------
key: b36d331451a61eb2d76860e00c347396
iv: e0fc2227c40bc9ea9343a1faafa4e23d
origin: 68656c6c6f
Ciphertext: a750a9ad00
Cleartext: dead0c78cf
当iv的长度不是96bit时,结果是错误的
我希望有人能告诉我哪里出了问题。
提前致谢。
这是预期的结果。如果您查看 Nist 800-38D,第 15 页,您会看到:
if len(IV)=96 then
Y0=IV||0^31||1
else
Y0=GHASH(H,{},IV).
因此,较大的 IV 不会被修剪,因此建议进行其他操作。这样,您的 IV 可以具有更高的熵。