从字符串生成密钥?

13

我需要从一个字符串生成一个密钥,使得我可以始终从相同的字符串创建相同的密钥。(具体来说是一个Key对象,以便我可以使用它创建Cipher,进而创建SealedObject)

在Java中是否可能实现这一目标,我应该查看哪个类/方法组合来完成?


hashCode() 对你来说不够用吗?如果不够用,为什么? - amit
http://en.wikipedia.org/wiki/Java_hashCode()#The_java.lang.String_hash_function - JProgrammer
据我所知,没有这样的功能,因为我正在尝试创建一个SealedObject以封装要传输的对象:我不是在尝试将明文字符串混淆成哈希值,而是在尝试创建一个密钥(对象)。 - Jon Story
4个回答

22

对于AES加密:

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

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);

byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

// reinit cypher using param spec
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));

对于与传统系统通信或学习目的,同样适用于已弃用的PBKDF1和不安全的DES:

byte[] salt = {
    (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
    (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
};

int count = 20;

PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

SealedObject sealed = new SealedObject(object, cipher);
...

注意,最后一个示例中的迭代次数也太少了。


谢谢。只是为了明确,如果我在服务器端使用相同的密码执行相同的操作,它会生成一个密文,可以用来解密SealedObject吗? - Jon Story
没错。只要您使用相同的参数和密钥规范,您将拥有相同的密钥。 - Eugene Kuleshov
4
如果去掉前半部分,这会成为一个更好的答案。DES 现在已经被完全攻破,甚至把它用作示例都是危险的(人们可能会不知情地复制它而不知道它是不安全的)。 - Rasmus Faber
1
DES 对于这些类型的事情来说已经非常过时了,它是可以被破解的。如果你在任何需要安全要求的东西中使用它,直接使用 DES 可能会给你带来麻烦。 - imichaelmiers
谢谢你们提供的额外细节。我一定会使用 AES 版本。 - Jon Story
它可以用于RSA吗? - abhishek maharajpet

5
你想要使用 PBKDF2 或者 bcrypt。在我的经验中,前者更为广泛使用。根据这个 comment,Java 支持这些加密方式。
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

2

这些都已经过时了。现在唯一推荐的算法是Argon2id。它在较新版本的Bouncycastle中提供:

https://www.bouncycastle.org/latest_releases.html

如果内存不足,请在执行参数中使用“-Xmx8G”。

private SecretKey genKey(char[] passwordChars, byte[] saltBytes) {
SecretKey aesKey;
    int aesKeyLen = 16; //key len in bytes
    int version = Argon2Parameters.ARGON2_VERSION_13;
    int iterations = 1;
    int memory = 22; // 20 = 1 GB -> 22=4GB
    int parallelism = 16; //double CPU core
    Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
            .withVersion(version).withIterations(iterations).withMemoryPowOfTwo(memory) // use 2^(memory) KB
            .withParallelism(parallelism).withSalt(saltBytes);
    Argon2BytesGenerator gen = new Argon2BytesGenerator();
    gen.init(builder.build());
    byte[] result = new byte[aesKeyLen];
    gen.generateBytes(passwordChars, result, 0, result.length);
    aesKey = new SecretKeySpec(result, "AES");
//clear to free RAM
    builder = null;
    gen = null;
    System.gc();
return aesKey;
}

我的意思是,这是一个九年前的问题……现在答案已经过时了,这并不完全令人惊讶。 - Jon Story

0

你可以通过Java加密来实现这个目标。

首先,您需要两个Jars:

  1. bcmail-jdk16-1.46.jar
  2. bcprov-jdk16-1.46.jar

以下是如何在Java中使用数据加密标准的完整示例:

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

import org.bouncycastle.util.encoders.Base64;


public class KeyGen {
    private SecretKey key;
    private Cipher ecipher;
    private Cipher dcipher;
    private static KeyGen keyGen;

    private KeyGen() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException{
        key = KeyGenerator.getInstance("DES").generateKey();
        ecipher = Cipher.getInstance("DES");
        dcipher = Cipher.getInstance("DES");
        ecipher.init(Cipher.ENCRYPT_MODE, key);
        dcipher.init(Cipher.DECRYPT_MODE, key);
    }

    public static KeyGen getInstance() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
        if(keyGen == null) {
            keyGen = new KeyGen();
        }
        return keyGen;
    }

    public String encrypt(String str) throws UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
        byte[] utf8 = str.getBytes("UTF8");
        byte[] enc = ecipher.doFinal(utf8);
        return new String(Base64.encode(enc));
    }

    public String decrypt(String str) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        byte[] dec = Base64.decode(str);
        byte[] utf8 = dcipher.doFinal(dec);
        return new String(utf8, "UTF8");
    }

    public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
        KeyGen keyGen = KeyGen.getInstance();
        String string = "JOYMAA";
        String enc = keyGen.encrypt(string);
        System.out.println(enc);
        String dec = keyGen.decrypt(enc);
        System.out.println(dec);
    }
}

使用方法:

KeyGen keyGen = KeyGen.getInstance();
String string = "JOYMAA";
String enc = keyGen.encrypt(string);
System.out.println(enc);
String dec = keyGen.decrypt(enc);
System.out.println(dec);

希望这能对你有所帮助。

ECB模式加密也不行。有一个名为KeyGen的类执行加密/解密也没有什么希望。 - Maarten Bodewes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接