加密和解密使用PBKDF2和AES256 – 需要实际示例 – 如何获得派生密钥

huangapple go评论77阅读模式
英文:

Encryption and Decryption with PBKDF2 and AES256 - practical example needed - how do I get the Derived key

问题

以下是翻译好的内容:

我正在尝试理解如何使用PBKDF2和SHA256获取派生密钥。

我陷入了困境,需要一个清晰易懂的示例。

到目前为止,我已经有以下内容:

  1. 我在https://en.wikipedia.org/wiki/PBKDF2 找到了一个示例,但是使用的是SHA1,具有以下值:

    密码 plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8

    A009C1A485912C6AE630D3E744240B04 HEX

    哈希函数:SHA1

    密钥大小:128

    迭代次数:1000

  2. 我一直在使用https://gchq.github.io/CyberChef,并且可以得到输出 17EB4014C8C461C300E9B61518B9A18B,这与维基百科示例中的派生密钥字节匹配。


我一直在使用https://mkyong.com/java/java-aes-encryption-and-decryption/,其中有一个名为getAESKeyFromPassword的方法,如下所示:

// 从密码派生的AES 256位秘钥
public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {

   SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
   // 迭代次数 = 65536
   // 密钥长度 = 256
   KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
   SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
   return secret;

}

我想要进行与维基百科页面、SHA1和CyberChef相同的“调查”,但是使用SHA256(替换Java代码中的值,以匹配示例中的盐、密码和迭代次数)。

这是我的困惑之处:

如果我要在CyberChef上使用与上述相同的值,但替换为SHA256:

密码 plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8

A009C1A485912C6AE630D3E744240B04 HEX

哈希函数:SHA256

密钥大小:128

迭代次数:1000

我会期望在CyberChef中得到的派生密钥与https://mkyong.com/java/java-aes-encryption-and-decryption/示例中的相同。

但事实并非如此。

我不禁认为我的理解存在缺陷。

请问是否可以提供一个简单的(详细解释的)PBKDF2与SHA256示例,以便我可以理解发生了什么。如果派生密钥与SHA1示例不相同,请解释原因。

Java的SecretKey:

SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

与派生密钥是相同的吗?

似乎缺乏易于理解的示例可供参考。

谢谢。

迈尔斯。

英文:

I am trying to understand how a derived key is obtained by using PBKDF2, with SHA256.

I am getting tangled up, and need a clear, easy to understand example.

What I have so far:

  1. I have found https://en.wikipedia.org/wiki/PBKDF2 which has a an example, but with SHA1, with the following values:

    PASSWORD plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8

    SALT A009C1A485912C6AE630D3E744240B04 HEX

    Hashing function SHA1

    Key Size 128

    Iterations 1000

  2. I have been using https://gchq.github.io/CyberChef and can get the output 17EB4014C8C461C300E9B61518B9A18B which matches the derived key bytes in the Wikipedia example.


I have been working with https://mkyong.com/java/java-aes-encryption-and-decryption/ which has a method named getAESKeyFromPassword, which is here:

// Password derived AES 256 bits secret key
public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    // iterationCount = 65536
    // keyLength = 256
    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    return secret;

}

I want to carry out the same "investigation", as I have done with the Wikipedia page, SHA1, and CyberChef, but using SHA256 (replacing the values in the Java code, to match the salt, password, iterations, from the example).

This is where my confusion starts:

If I were to use CyberChef to work on the same values as above, but replace with SHA256:

PASSWORD plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd UTF8

SALT A009C1A485912C6AE630D3E744240B04 HEX

Hashing function SHA256

Key Size 128

Iterations 1000

I would expect the derived key to be the same in CyberChef, as the https://mkyong.com/java/java-aes-encryption-and-decryption/ example.

It's not.

I cannot help but think there is a flaw in my understanding.

Can someone please provide a simple (worked-through) example of PBKDF2 with SHA256, so I can understand what is going on. If the derived key is not meant to be the same (as with the SHA1 example, please explain why).

Is the Java SecretKey:

SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

The same as the derived key?

There seems to be a lack of easy-to-understand examples to follow.

Thanks

Miles.

答案1

得分: 1

感谢大家的贡献,特别是 Topaco 加密和解密使用PBKDF2和AES256 – 需要实际示例 – 如何获得派生密钥

我将回答我的问题,因为我花了一些时间在一个最小可复现示例(MCVE)上进行工作,并且已经成功获取了与 cyberChef 相同的 SecretKey。

秘密密钥的值是:28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a

以下是来自 cyberChef 的输出:

加密和解密使用PBKDF2和AES256 – 需要实际示例 – 如何获得派生密钥

以下是 Java 代码以及运行它时的输出:

package crypto;

import ...
//(以下省略,为了节省空间,显示部分代码)

public class EncryptDecryptAesGcmPassword {
    //(以下省略,为了节省空间,显示部分代码)

    public static void main(String[] args) throws Exception {
        String OUTPUT_FORMAT = "%-30s:%s";
        String PASSWORD = "plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd";
        //(以下省略,为了节省空间,显示部分代码)

        //(以下省略,为了节省空间,显示部分代码)

        String decryptedText = EncryptDecryptAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD);
        System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
    }
}

运行这段代码,将产生以下输出:

SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a

------ AES GCM 基于密码的加密 ------
输入(明文):AES-GSM Password-Bases encryption!
加密(base64):/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4

------ AES GCM 基于密码的解密 ------
输入(base64):/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4
SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a
解密(明文):AES-GSM Password-Bases encryption!

谢谢

Miles。

英文:

Thank you all for your input, especially Topaco 加密和解密使用PBKDF2和AES256 – 需要实际示例 – 如何获得派生密钥

I am going to answer my question, as I have spent some time working on a MCVE, and have managed to get the same SecretKey as cyberChef.

The secret key value is: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a

Here is the output from cyberChef:

加密和解密使用PBKDF2和AES256 – 需要实际示例 – 如何获得派生密钥

Here is the Java code, and output from running it:

package crypto;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

public class EncryptDecryptAesGcmPassword {

    private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

    private static final int TAG_LENGTH_BIT = 128; // must be one of {128, 120,     112, 104, 96}
    private static final int IV_LENGTH_BYTE = 12;
    private static final int SALT_LENGTH_BYTE = 16;
    public static final int ITERATION_COUNT = 1000;
    public static final int KEY_LENGTH = 256;

    private static final Charset UTF_8 = StandardCharsets.UTF_8;

    // return a base64 encoded AES encrypted text
    public static String encrypt(byte[] salt, byte[] pText, String password) throws Exception {
        // GCM recommended 12 bytes iv?
        byte[] iv = getRandomNonce(IV_LENGTH_BYTE);

        // secret key from password
        SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        // ASE-GCM needs GCMParameterSpec
        cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));

        byte[] cipherText = cipher.doFinal(pText);

        // prefix IV and Salt to cipher text
        byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
            .put(iv)
            .put(salt)
            .put(cipherText)
            .array();

        // string representation, base64, send this string to other for decryption.
        return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);

    }

    // we need the same password, salt and iv to decrypt it
    private static String decrypt(String cText, String password) throws Exception {
        byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8));

        // get back the iv and salt from the cipher text
        ByteBuffer bb = ByteBuffer.wrap(decode);

        byte[] iv = new byte[IV_LENGTH_BYTE];
        bb.get(iv);

        byte[] salt = new byte[SALT_LENGTH_BYTE];
        bb.get(salt);

        byte[] cipherText = new byte[bb.remaining()];
        bb.get(cipherText);

        // get back the aes key from the same password and salt
        SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);

        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

        cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));

        byte[] plainText = cipher.doFinal(cipherText);

        return new String(plainText, UTF_8);

    }


    public static byte hexToByte(String hexString) {
        int firstDigit = toDigit(hexString.charAt(0));
        int secondDigit = toDigit(hexString.charAt(1));
        return (byte) ((firstDigit << 4) + secondDigit);
    }

    public static byte[] decodeHexString(String hexString) {
        if (hexString.length() % 2 == 1) {
            throw new IllegalArgumentException(
                "Invalid hexadecimal String supplied.");
        }

        byte[] bytes = new byte[hexString.length() / 2];
        for (int i = 0; i < hexString.length(); i += 2) {
            bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
        }  
        return bytes;
    }

    private static int toDigit(char hexChar) {
        int digit = Character.digit(hexChar, 16);
        if (digit == -1) {
            throw new IllegalArgumentException(
                "Invalid Hexadecimal Character: "+ hexChar);
        }
        return digit;
    }

    // Random byte[] with length numBytes
    public static byte[] getRandomNonce(int numBytes) {
        byte[] nonce = new byte[numBytes];
        new SecureRandom().nextBytes(nonce);
        return nonce;
    }

    // Password derived AES 256 bits secret key
    public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

        SecretKeyFactory factory =    SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        // iterationCount = 1000
        // keyLength = 256
        KeySpec spec = new PBEKeySpec(password, salt, ITERATION_COUNT,
            KEY_LENGTH);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

        String encodedKey = hex(secret.getEncoded());

        // print SecretKey as hex
        System.out.println("SecretKey: " + encodedKey);

        return secret;

    }

    // hex representation
    public static String hex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }



    public static void main(String[] args) throws Exception {
        String OUTPUT_FORMAT = "%-30s:%s";
        String PASSWORD = "plnlrtfpijpuhqylxbgqiiyipieyxvfsavzgxbbcfusqkozwpngsyejqlmjsytrmd";

        // plain text
        String pText = "AES-GSM Password-Bases encryption!";

        // convert hex string to byte[]
        byte[] salt = decodeHexString("A009C1A485912C6AE630D3E744240B04");


        String encryptedTextBase64 = EncryptDecryptAesGcmPassword.encrypt(salt, pText.getBytes(UTF_8), PASSWORD);

        System.out.println("\n------ AES GCM Password-based Encryption ------");
        System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));
        System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64));

        System.out.println("\n------ AES GCM Password-based Decryption ------");
        System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64));

        String decryptedText = EncryptDecryptAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD);
        System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
    }
}

Running this code, produces the following:

SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a

------ AES GCM Password-based Encryption ------
Input (plain text)            :AES-GSM Password-Bases encryption!
Encrypted (base64)            :/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4

------ AES GCM Password-based Decryption ------
Input (base64)               :/PuTLBTKVWgJB2iMoAnBpIWRLGrmMNPnRCQLBABOkwNeY8BrrdtoRNVFqZ+xmUjvF2PET6Ne2+PAp34QLCUFjQodTMdmzaNAfzcLWOf4
SecretKey: 28869b5f31ae29236f164c5cb33e2e3bb46f483867a15f8e7208e1836070f64a
Decrypted (plain text)        :AES-GSM Password-Bases encryption!

Thanks

Miles.

huangapple
  • 本文由 发表于 2020年10月11日 00:05:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/64295501.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定