没有身份验证标签的 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 可以具有更高的熵。