将秘密密钥转换为字符串及其反向操作

137

我正在生成一个密钥,并需要将其存储在数据库中,因此我将其转换为字符串,但是要从字符串中获取密钥,有哪些可能的方法可以实现?

我的代码如下:

SecretKey key = KeyGenerator.getInstance("AES").generateKey();
String stringKey=key.toString();
System.out.println(stringKey);

我该如何从字符串中获取键值?


2
请注意,仅在绝对必要时才应将键转换为字符串。在Java中,没有显式的方法来销毁String实例,而键对象和字节数组可以被清除。这意味着键可能会在内存中保持更长时间。最好使用(受密码保护的)KeyStore,最好是由运行时系统/操作系统或甚至硬件支持的KeyStore - Maarten Bodewes
6个回答

336

您可以将 SecretKey 转换为字节数组 (byte[]),然后对其进行 Base64 编码以获得一个 String。要将其转换回 SecretKey,请对该字符串进行 Base64 解码,并在 SecretKeySpec 中使用它来重建原始的 SecretKey

对于Java 8

将 SecretKey 转换为 String:

// create new key
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
// get base64 encoded version of the key
String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded());

将字符串转换为SecretKey:

// decode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
// rebuild key using SecretKeySpec
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); 

对于Java 7及以前的版本(包括Android):

注意1:你可以跳过Base64编码/解码部分,直接将byte[]存储在SQLite中。不过,执行Base64编码/解码并不是一项昂贵的操作,你可以在几乎任何DB中存储字符串而不会出现问题。

注意2:早期的Java版本没有在java.langjava.util包中提供Base64。但可以使用来自Apache Commons CodecBouncy CastleGuava的编解码器。

将SecretKey转换为字符串:

// CREATE NEW KEY
// GET ENCODED VERSION OF KEY (THIS CAN BE STORED IN A DB)

    SecretKey secretKey;
    String stringKey;

    try {secretKey = KeyGenerator.getInstance("AES").generateKey();}
    catch (NoSuchAlgorithmException e) {/* LOG YOUR EXCEPTION */}

    if (secretKey != null) {stringKey = Base64.encodeToString(secretKey.getEncoded(), Base64.DEFAULT)}

字符串转SecretKey:

// DECODE YOUR BASE64 STRING
// REBUILD KEY USING SecretKeySpec

    byte[] encodedKey     = Base64.decode(stringKey, Base64.DEFAULT);
    SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");

1
@Jabari,“Base64”类的包是什么? - Swap L
@MaartenBodewes-owlstead 大多数人还没有开始使用Java 8。我在Android中使用了它,而Android肯定还没有使用Java 8(而且可能要等一段时间)。请不要根据上下文的假设编辑别人的答案。 - Jabari
@MaartenBodewes-owlstead,你的评论完全忽略了我的第一句话:“大多数人还没有使用Java 8”。你的答案会对绝大多数Java用户(包括Android和非Android用户)抛出异常错误。话虽如此,你建议在当前答案中添加代码片段将提供更完整的解决方案。顺便说一下,我对我的答案并不“感情用事”。事实上,我将DES换成AES是因为它在安全性方面有明显的改进(同时也更符合原始问题中的代码)。 - Jabari
我知道,任何了解Java的人都知道如何处理已检查异常,但是我将创建一个“IllegalStateException”,因为AES应始终存在... - Maarten Bodewes
嗨Jabari,当我执行SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");时,我会收到错误提示需要SecretKey,提供的是SecretKeySpec。请更新解决方案。谢谢。 - Because i hate myself
显示剩余3条评论

9
为了展示创建一些“快速失败”函数的乐趣,我编写了以下三个函数。
其中一个函数用于创建AES密钥,另外两个函数分别用于加密和解密。这三个方法可以在Java 8中使用(不依赖内部类或外部依赖项)。
public static SecretKey generateAESKey(int keysize)
        throws InvalidParameterException {
    try {
        if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
            // this may be an issue if unlimited crypto is not installed
            throw new InvalidParameterException("Key size of " + keysize
                    + " not supported in this runtime");
        }

        final KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(keysize);
        return keyGen.generateKey();
    } catch (final NoSuchAlgorithmException e) {
        // AES functionality is a requirement for any Java SE runtime
        throw new IllegalStateException(
                "AES should always be present in a Java SE runtime", e);
    }
}

public static SecretKey decodeBase64ToAESKey(final String encodedKey)
        throws IllegalArgumentException {
    try {
        // throws IllegalArgumentException - if src is not in valid Base64
        // scheme
        final byte[] keyData = Base64.getDecoder().decode(encodedKey);
        final int keysize = keyData.length * Byte.SIZE;

        // this should be checked by a SecretKeyFactory, but that doesn't exist for AES
        switch (keysize) {
        case 128:
        case 192:
        case 256:
            break;
        default:
            throw new IllegalArgumentException("Invalid key size for AES: " + keysize);
        }

        if (Cipher.getMaxAllowedKeyLength("AES") < keysize) {
            // this may be an issue if unlimited crypto is not installed
            throw new IllegalArgumentException("Key size of " + keysize
                    + " not supported in this runtime");
        }

        // throws IllegalArgumentException - if key is empty
        final SecretKeySpec aesKey = new SecretKeySpec(keyData, "AES");
        return aesKey;
    } catch (final NoSuchAlgorithmException e) {
        // AES functionality is a requirement for any Java SE runtime
        throw new IllegalStateException(
                "AES should always be present in a Java SE runtime", e);
    }
}

public static String encodeAESKeyToBase64(final SecretKey aesKey)
        throws IllegalArgumentException {
    if (!aesKey.getAlgorithm().equalsIgnoreCase("AES")) {
        throw new IllegalArgumentException("Not an AES key");
    }

    final byte[] keyData = aesKey.getEncoded();
    final String encodedKey = Base64.getEncoder().encodeToString(keyData);
    return encodedKey;
}

2
请注意,如果密钥存储在硬件安全模块(或任何其他位置不支持getEncoded()方法),则存储/检索密钥可能无法正常工作。 - Maarten Bodewes

2

实际上,Luis提出的方法对我没有用。我不得不想出另一种方法。这是帮助我的方法,也可能对您有所帮助。 链接:

  1. *.getEncoded(): https://docs.oracle.com/javase/7/docs/api/java/security/Key.html

  2. 编码器信息:https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Encoder.html

  3. 解码器信息:https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html

代码片段: 用于编码:

String temp = new String(Base64.getEncoder().encode(key.getEncoded()));

解码:

byte[] encodedKey = Base64.getDecoder().decode(temp);
SecretKey originalKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "DES");

0

将SecretKeySpec转换为字符串和反之: 您可以使用SecretKeySpec中的getEncoded()方法,它将给出byteArray,从中您可以使用encodeToString()来获取Base64对象中SecretKeySpecstring值。

在将SecretKeySpec转换为String时:使用Base64中的decode()将给出byteArray,从中您可以创建一个实例,参数为byteArray,以重现您的SecretKeySpec

String mAesKey_string;
SecretKeySpec mAesKey= new SecretKeySpec(secretKey.getEncoded(), "AES");

//SecretKeySpec to String 
    byte[] byteaes=mAesKey.getEncoded();
    mAesKey_string=Base64.encodeToString(byteaes,Base64.NO_WRAP);

//String to SecretKeySpec
    byte[] aesByte = Base64.decode(mAesKey_string, Base64.NO_WRAP);
    mAesKey= new SecretKeySpec(aesByte, "AES");

0

您不想使用.toString()

请注意,SecretKey继承自java.security.Key,后者又继承自Serializable。因此,关键(无意双关语)是将密钥序列化为ByteArrayOutputStream,获取byte[]数组并将其存储到数据库中。反向过程是从数据库获取byte[]数组,创建一个ByteArrayInputStream,并对其进行反序列化以获取SecretKey...

...或者更简单地,只需使用继承自java.security.Key的.getEncoded()方法(它是SecretKey的父接口之一)。该方法返回Key/SecretKey的编码byte[]数组,您可以将其存储或从数据库中检索。

所有这些都假定您的SecretKey实现支持编码。否则,getEncoded()将返回null。

编辑:

您应该查看Key/SecretKey javadocs(在谷歌页面的开头就可找到):

http://download.oracle.com/javase/6/docs/api/java/security/Key.html

或者这个来自CodeRanch(也可以通过相同的谷歌搜索找到):

http://www.coderanch.com/t/429127/java/java/Convertion-between-SecretKey-String-or


在我看来,Serializable是一种反模式,只要你有替代方案。使用base64编码和解码的批准答案要好得多。 - craigmiller160

0

尝试这个,它可以在没有Base64的情况下工作(Base64仅包含在JDK 1.8中),这段代码也可以在之前的Java版本中运行 :)

private static String SK = "Secret Key in HEX";


//  To Encrupt

public static String encrypt( String Message ) throws Exception{

    byte[] KeyByte = hexStringToByteArray( SK);
    SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");

    Cipher c = Cipher.getInstance("DES","SunJCE");
    c.init(1, k);
    byte mes_encrypted[] = cipher.doFinal(Message.getBytes());

    String MessageEncrypted = byteArrayToHexString(mes_encrypted);
    return MessageEncrypted;
}

//  To Decrypt

public static String decrypt( String MessageEncrypted )throws Exception{

    byte[] KeyByte = hexStringToByteArray( SK );
    SecretKey k = new SecretKeySpec(KeyByte, 0, KeyByte.length, "DES");

    Cipher dcr =  Cipher.getInstance("DES","SunJCE");
    dc.init(Cipher.DECRYPT_MODE, k);
    byte[] MesByte  = hexStringToByteArray( MessageEncrypted );
    byte mes_decrypted[] = dcipher.doFinal( MesByte );
    String MessageDecrypeted = new String(mes_decrypted);

    return MessageDecrypeted;
}

public static String byteArrayToHexString(byte bytes[]){

    StringBuffer hexDump = new StringBuffer();
    for(int i = 0; i < bytes.length; i++){
    if(bytes[i] < 0)
    {   
        hexDump.append(getDoubleHexValue(Integer.toHexString(256 - Math.abs(bytes[i]))).toUpperCase());
    }else
    {
        hexDump.append(getDoubleHexValue(Integer.toHexString(bytes[i])).toUpperCase());
    }
    return hexDump.toString();

}



public static byte[] hexStringToByteArray(String s) {

    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2)
    {   
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
    }
    return data;

} 

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