java - 恢复加密代理 key

标签 java encryption aes password-encryption secret-key

我正在尝试使用 AES 加密来实现 this 加密方案。

基本上是这样的:

  1. 用户数据使用代理 key (surrogateKey) 加密
  2. 代理 key 与从密码派生的 key 进行异或运算 (passwordKey) 和存储的 (storedKey)
  3. 当需要 key (加密或解密用户数据)时,将从数据库中检索storedKey,并与新生成的passwordKey再次进行异或以恢复surrogateKey

除了,我一定在实现中做错了什么,因为我似乎永远无法恢复有效的代理 key ,并且任何解密尝试都会给我一个 BadPaddingException。

下面的代码演示了该问题。抱歉,如果它有点长,但您应该可以将其复制并粘贴到您的 IDE 中。`

import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class SurrogateTest {
    private static final String alphanumeric =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                    + "abcdefghijklmnopqrstuvwxyz"
                    + "1234567890"
                    + "!#$%&()+*<>?_-=^~|";

    private static String plainText = "I am the very model of a modern major general";
    private static String cipherText = "";
    private static SecureRandom rnd = new SecureRandom();

    private static byte[] salt;

    public static void main(String[] args) {
        // arguments are password and recovery string
        if (args.length > 1) {
            System.out.println("password: " + args[0] + "; recovery: " + args[1]);
        }
        String password = args[0];

        // passwordKey
        SecretKey passwordKey = getKey(password);
        System.out.println("passwordKey: " + DatatypeConverter.printBase64Binary(passwordKey.getEncoded()));

        // Generate surrogate encryption key from random string
        String rand = randomString(24);
        SecretKey surrogateKey = getKey(rand);
        byte[] surrogateByteArray = surrogateKey.getEncoded();
        System.out.println("surrogate: " + DatatypeConverter.printBase64Binary(surrogateByteArray));

        // encrypt plainText
        System.out.println("text to encrypt: " + plainText);
        cipherText = encryptWithKey(plainText, surrogateKey);

        // XOR surrogateKey with passwordKey to get storedKey
        SecretKey storedKey = xorWithKey(surrogateKey, passwordKey);
        String storedKeyString = DatatypeConverter.printBase64Binary(storedKey.getEncoded());
        System.out.println("storedKey: " + storedKeyString);

        byte[] storedKey2Array = DatatypeConverter.parseBase64Binary(storedKeyString);
        SecretKey storedKey2 = new SecretKeySpec(storedKey2Array, 0, storedKey2Array.length, "AES");
        String storedKey2String = DatatypeConverter.printBase64Binary(storedKey2.getEncoded());
        System.out.println("storedKey->String->key->string: " + storedKey2String);

        // recover surrogateKey from storedKey2
        SecretKey password2Key = getKey(password);
        System.out.println("password2Key: " + DatatypeConverter.printBase64Binary(password2Key.getEncoded()));
        SecretKey surrogate2Key = xorWithKey(storedKey2, password2Key);
        System.out.println("surrogate2 (recovered): " + DatatypeConverter.printBase64Binary(surrogate2Key.getEncoded()));

        // decrypt text
        String decryptedText = decryptWithKey(cipherText, surrogate2Key);
        System.out.println("decryptedText: " + decryptedText);
    }

    private static SecretKey xorWithKey(SecretKey a, SecretKey b) {
        byte[] out = new byte[b.getEncoded().length];
        for (int i = 0; i < b.getEncoded().length; i++) {
            out[i] = (byte) (b.getEncoded()[i] ^ a.getEncoded()[i % a.getEncoded().length]);
        }
        SecretKey outKey = new SecretKeySpec(out, 0, out.length, "AES");

        return outKey;
    }

    private static String randomString(int length) {
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++)
            sb.append(alphanumeric.charAt(rnd.nextInt(alphanumeric.length())));
        return sb.toString();
    }

    // return encryption key
    private static SecretKey getKey(String password) {
        try {
            SecureRandom random = new SecureRandom();
            salt = new byte[16];
            random.nextBytes(salt);

            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            // obtain secret key
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

            return secret;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String encryptWithKey(String str, SecretKey secret) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);
            AlgorithmParameters params = cipher.getParameters();
            byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
            byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); // encrypt the message str here

            // concatenate salt + iv + ciphertext
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            outputStream.write(salt);
            outputStream.write(iv);
            outputStream.write(encryptedText);

            // properly encode the complete ciphertext
            String encrypted = DatatypeConverter.printBase64Binary(outputStream.toByteArray());
            return encrypted;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String decryptWithKey(String str, SecretKey secret) {
        try {
            byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);
            if (ciphertext.length < 48) {
                return null;
            }
            salt = Arrays.copyOfRange(ciphertext, 0, 16);
            byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32);
            byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
            byte[] plaintext = cipher.doFinal(ct);

            return new String(plaintext, "UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

知道我做错了什么吗?

最佳答案

运行发布的代码显示surrogate2(已恢复)surrogate不同。显然这是严重错误的。原因是在加密时,您使用随机盐派生“密码”(包装) key ,并将该盐写入数据 blob 的开头;在解密时,您使用派生解包 key ,然后从 blob 中读取正确的盐并完全忽略它。这意味着你的解包 key 是错误的,所以你的解包数据 key 是错误的,所以你的解密是错误的。

PS:用于直接加密和解密数据的随 secret 钥通常称为“数据” key (就像我刚才所做的那样)或DEK(数据加密或加密 key 的缩写),或者更具体的术语,例如“ session ” key”或“message key”(在本例中),或“working key”或“transient key”以强调其有限范围。它通常不被称为“代理”。并且使用 PBKDF2 从强随机字符串中导出该数据 key 是浪费时间;只需直接使用 SecureRandom_instance.nextBytes(byte[]) 作为数据 key 即可。

关于java - 恢复加密代理 key ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48319909/

相关文章:

aes - 从 C++ 代码和命令行生成相同的 key 和 IV

c# - PBKDF2 Python key 与 .NET Rfc2898

java - 区分 JDBC 驱动程序异常和数据库异常

java - 有没有一种简洁的方法可以根据 X.getY() 的顺序创建 Comparator<X>

java - 在优先考虑源代码保护的情况下加密 JAR

python - Python 中的混洗位

cocoa - 如何在 cocoa 应用程序中存储文件然后稍后访问它以加密

java - 使用 Iterator 解析 JSON 并相应地在 listview 中设置它

java - JBehave 似乎没有执行测试

asp.net - System.Security.Cryptography.CryptographicException : Not enough storage is available to process this command