恢复加密代理密钥

时间:2018-01-18 11:11:41

标签: java encryption aes password-encryption secret-key

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

基本上如下:

  1. 用代理键(surrogateKey)加密用户数据
  2. 使用从密码派生的密钥对代理键进行异或 (passwordKey)和存储(storedKey)
  3. 当需要密钥(加密或解密用户数据)时,从DB检索storedKey并使用新生成的passwordKey再次进行异或,以恢复surrogateKey
  4. 除此之外,我必须在实现中做错事,因为我似乎无法恢复有效的surrogateKey,并且任何解密尝试都会给我一个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;
        }
    }
    

    任何想法我做错了什么?

1 个答案:

答案 0 :(得分:1)

运行发布的代码会显示surrogate2 (recovered)surrogate不同。显然这是非常错误的。原因是在加密时你得到了密码&#39; (包装)密钥使用随机盐并在数据blob的开头写入该盐;在解密时,您使用 new salt 派生解包密钥,然后从blob中读取正确的salt并完全忽略它。这意味着您的解包密钥错误,因此您的解包数据密钥错误,因此您的解密错误。

PS:用于直接加密和解密数据的随机密钥通常称为数据&#39;密钥(正如我刚才所做)或DEK(数据加密或加密密钥的缩写),或更具体的术语,如会话密钥&#39;或者&#39;消息键&#39; (在这种情况下),或者工作密钥&#39;或者&#39;瞬态键&#39;强调其有限的范围。它通常不被称为“替代品”。使用PBKDF2从强随机字符串派生此数据密钥是浪费时间;只需直接使用SecureRandom_instance.nextBytes(byte[])作为数据密钥。