Java使用AES 256和128对称密钥加密

15

我新接触密码技术。我找到了这段代码可以进行对称加密。

byte[] key = //... secret sequence of bytes
byte[] dataToSend = ...
Cipher c = Cipher.getInstance("AES");
SecretKeySpec k = new SecretKeySpec(key, "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal(dataToSend);

它正在工作。在这里我可以使用自己的密码。这正是我所需要的。但是我不知道如何进行128或256对称加密。如何将128位和256位密钥用于我的代码?


我真的不知道,但我认为使用256字节的密钥就可以完成任务。 - Martijn Courteaux
实际上,只有16字节(128位)的密钥可以在我的代码中工作。 - Bhuban
使用更大的键时出现了什么问题?您是否收到了异常?是哪一个?它的消息和堆栈跟踪是什么? - JB Nizet
可能是因为我没有安装JCE。 - Bhuban
5个回答

18
无论是使用128位还是256位的AES取决于您的密钥大小,其长度必须为128位或256位。通常情况下,您不会将密码用作密钥,因为密码很少具有所需的确切长度。相反,您可以使用一些密钥派生函数从密码中派生出加密密钥。
一个非常简单的例子是:使用MD5对密码进行哈希处理以获得128位密钥如果您需要256位密钥,可以使用SHA-256对密码进行哈希处理以获取256位哈希值。密钥派生函数通常会运行这个哈希函数多次并使用额外的盐。请查看http://en.wikipedia.org/wiki/Key_derivation_function了解详情。
另外请注意:要使用比128位更强的加密,您需要从http://www.oracle.com/technetwork/java/javase/downloads/index.html下载和安装“Java密码扩展(JCE)无限制权限策略文件6”。

7
MD5是什么?今天早上我是不是回到了1996年?SHA系列有产生128、256、348和512位哈希值的算法。 - Petey B
5
@Peter:这只是一个快速示例,演示如何从密码计算出128位密钥。如果您的密码少于16字节,您也可以将其填充到16字节。您在纠结不重要的细节。(无论如何,我已经更新了我的答案,建议使用SHA-256从密码获取256位密钥)。 - Peter Štibraný
1
@Peter,哈哈哈,是啊,我只是讨厌在除了遗留代码之外的任何地方看到MD5,并且不认为应该向初学者推荐它;它已经被破解很久了。 - Petey B
5
@Peter: :-) 它对某些用途来说是“破损的”...但在这种情况下,选择什么哈希算法并不是非常重要。你想保护你的密钥,无论你如何获得它。就像你的密钥(密码的md5散列值)是公开的,有人想知道你的密码一样。一旦有人拥有了你的密钥(从我的例子中得出的md5散列值),你就完了。而MD5与此关系不大。 - Peter Štibraný
3
在Peter Stibrany所描述的情况下使用MD5是没有问题的。MD5具有漏洞,使其不适用于许多情况,但本情况不包括在内。一般来说,从密码派生密钥应使用像PBKDF2(默认情况下使用SHA-1),bcrypt或scrypt之类的密钥派生算法。 - Anton
显示剩余3条评论

8

128位加密的解决方案

以下方法是使用AES加密算法对字符串(valueEnc)进行加密:

private static final String ALGORITHM = "AES"; 

public String encrypt(final String valueEnc, final String secKey) { 

    String encryptedVal = null;

    try {
        final Key key = generateKeyFromString(secKey);
        final Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.ENCRYPT_MODE, key);
        final byte[] encValue = c.doFinal(valueEnc.getBytes());
        encryptedVal = new BASE64Encoder().encode(encValue);
    } catch(Exception ex) {
        System.out.println("The Exception is=" + ex);
    }

    return encryptedVal;
}

下面的方法将解密AES加密的字符串(encryptedVal):
    public String decrypt(final String encryptedValue, final String secretKey) {

    String decryptedValue = null;

    try {

        final Key key = generateKeyFromString(secretKey);
        final Cipher c = Cipher.getInstance(ALGORITHM);
        c.init(Cipher.DECRYPT_MODE, key);
        final byte[] decorVal = new BASE64Decoder().decodeBuffer(encryptedValue);
        final byte[] decValue = c.doFinal(decorVal);
        decryptedValue = new String(decValue);
    } catch(Exception ex) {
        System.out.println("The Exception is=" + ex);
    }

    return decryptedValue;
}

secKey 是一个128位的密钥,它被编码在 BASE64Encoder 中。下面的方法中的 BASE64Decoder 生成了一个合适的128位密钥。

private Key generateKeyFromString(final String secKey) throws Exception {
    final byte[] keyVal = new BASE64Decoder().decodeBuffer(secKey);
    final Key key = new SecretKeySpec(keyVal, ALGORITHM);
    return key;
}

2
您忘记使用初始化向量,也没有指定您想要使用的AES模式和填充。虽然这是对问题的回答,但请注意,这不是加密的安全使用方式。 - Jesse van Bekkum

3
你可以使用一个简单的KeyGenerator对象,如下所示:
KeyGenerator generator = KeyGenerator.getInstance("AES/CTR/PKCS5PADDING");
generator.init(128);
SecretKey key = generator.generateKey();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
...

1

来自Java的Cipher.init(...)文档

public final void init(int opmode, Key key)

抛出: InvalidKeyException - 如果给定的密钥不适合初始化此密码,或者如果此密码正在被初始化以进行解密并且需要无法从给定密钥确定的算法参数,或者如果给定密钥的密钥大小超过最大允许的密钥大小(根据配置的司法管辖区策略文件确定)。

对我来说,这意味着像Martijn Courteaux在他的评论中所说的那样,您应该使用256位的密钥(即使用包含32个字节的字节数组初始化SecretKeySpec),并且加密器将接受它并使用它,或者如果其大小不可接受,则拒绝它并抛出异常。

如果您遇到异常,可能是因为您尚未安装无限制强度的加密文件(默认JDK安装允许128位密钥,如加密规范文档所述)。请在此处下载无限制强度的加密包。


0
public class CipherUtils
{
    private static byte[] key = {
            0x74, 0x68, 0x69, 0x73, 0x49, 0x73, 0x41, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79
    };//"thisIsASecretKey";

    public static String encrypt(String strToEncrypt)
    {
        try
        {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            final SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            final String encryptedString = Base64.encodeBase64String(cipher.doFinal(strToEncrypt.getBytes()));
            return encryptedString;
        }
        catch (Exception e)
        {
           e.printStackTrace();
        }
        return null;
    }
}

我有几个问题。
  1. 那个密钥是唯一能用于解密的密钥,对吗?(可能这个问题的答案很明显,只是想确保一下)
  2. 如果用相同的密钥对相同的数据进行加密——两次运行方法——会生成不同的加密字节吗?
- raddevus
我转换了那段代码并自己找到了答案。另外,有趣的是OWASP的例子(https://www.owasp.org/index.php/Using_the_Java_Cryptographic_Extensions)直接提到使用IV(初始化向量),但在这个答案的代码中没有使用。这就是生活。 - raddevus
不要硬编码一个秘密密钥。这将轻易地暴露给访问已编译代码的任何人。 - Mike

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