使用RSA公钥加密AES密钥

6

我正在编写一个小型的文件传输应用程序,主要是为了更深入地学习编程加密基础知识。我的想法是生成RSA密钥对,交换公钥,并发送AES iv和key以进行进一步解密。我希望使用接收者的RSA公钥来加密AES key,方法如下:

// encode the SecretKeySpec
private byte[] EncryptSecretKey ()
{
    Cipher cipher = null;
    byte[] key = null;

    try
    {
        cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
        // contact.getPublicKey returns a public key of type Key
        cipher.init(Cipher.ENCRYPT_MODE, contact.getPublicKey() );
        // skey is the SecretKey used to encrypt the AES data
        key = cipher.doFinal(skey.getEncoded());
    }
    catch(Exception e )
    {
        System.out.println ( "exception encoding key: " + e.getMessage() );
        e.printStackTrace();
    }
    return key;
}

我会将密钥值发送给接收方,然后进行解密操作:

private SecretKey decryptAESKey(byte[] data )
{
    SecretKey key = null;
    PrivateKey privKey = null;
    Cipher cipher = null;

    System.out.println ( "Data as hex: " + utility.asHex(data) );
    System.out.println ( "data length: " + data.length );
    try
    {
        // assume this loads our private key
        privKey = (PrivateKey)utility.loadLocalKey("private.key", false);

        cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
        cipher.init(Cipher.DECRYPT_MODE, privKey );
        key = new SecretKeySpec(cipher.doFinal(data), "AES");

        System.out.println ( "Key decrypted, length is " + key.getEncoded().length );
        System.out.println ( "data: " + utility.asHex(key.getEncoded()));
    }
    catch(Exception e)
    {
        System.out.println ( "exception decrypting the aes key: " + e.getMessage() );
        e.printStackTrace();
        return null;
    }

    return key;
}

在控制台中,另一方面,我得到如下输出:

read_bytes for key: 16
data length: 16
Data as hex: <hex string>
Key decrypted, length is 256
java.security.InvalidKeyException: Invalid AES key length: 256 bytes

此外,如果我创建一个大小为16的字节数组,并将cipher.doFinal(data)的输出放入其中,则该数组似乎被调整为256个字节(至少.length这样说)。这是为什么呢?并且更进一步,我做错了什么吗?
编辑 我已经解决了这个问题,并认为我应该发布此问题以防有人遇到。问题实际上是RSA / ECB / NOPADDING。由于某种奇怪的原因,当我将SecretKey传输到客户端时,它会破坏我的创建。这可能与我如何生成密钥对有关(我正在使用getInstance(“RSA”)),但我不确定。

1
嗨,帽子,请将您的完整答案发布为答案,而不是在问题中进行编辑(您告诉我们问题是什么,而不是答案)。然后您可以在一段时间后接受自己的答案。或者接受我的答案,其中解释了为什么应该使用"RSA/ECB/PKCS1Padding"而不是"RSA/ECB/NoPadding"... - Maarten Bodewes
请注意,在创建执行加密的在线协议时,应使用完整性保护(例如签名)。即使是RSA加密也可能容易受到填充预言攻击等攻击。如果没有完整性保护,则会出现常见错误,尽管如果使用PKCS#1 v1.5(由“RSA / ECB / PKCS1Padding”隐含),则此问题受到限制。 - Maarten Bodewes
顺便提一下,ECB 很少是块密码的好模式。 - CodesInChaos
据我所知,核心的javax.crypto库不支持任何其他形式的分组密码,因此我有点束手无策。 - hat
使用密钥封装在对称密钥传输中使用RSA(即RSA-KEM)是一个好主意。 - vlp
显示剩余2条评论
3个回答

5
如owlstead所述,您不能只使用未填充的"原始"RSA进行加密/解密。首先,这非常不安全;其次,Java库甚至不支持它。以下是使用RSA密钥对加密/解密AES密钥的有效代码。
private byte[] EncryptSecretKey ()
{
    Cipher cipher = null;
    byte[] key = null;

    try
    {
        // initialize the cipher with the user's public key
        cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, contact.getPublicKey() );
        key = cipher.doFinal(skey.getEncoded());
    }
    catch(Exception e )
    {
        System.out.println ( "exception encoding key: " + e.getMessage() );
        e.printStackTrace();
    }
    return key;
}

AES密钥解密的过程如下:

private SecretKey decryptAESKey(byte[] data )
{
    SecretKey key = null;
    PrivateKey privKey = null;
    Cipher cipher = null;

    try
    {
        // this is OUR private key
        privKey = (PrivateKey)utility.loadLocalKey(
                                ConfigFrame.privateKeyLocation, false);

        // initialize the cipher...
        cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privKey );

        // generate the aes key!
        key = new SecretKeySpec ( cipher.doFinal(data), "AES" );
    }
    catch(Exception e)
    {
        System.out.println ( "exception decrypting the aes key: " 
                                               + e.getMessage() );
        return null;
    }

    return key;
}

1
嗨,我进行了小的编辑,明确依赖于“RSA/ECB/PKCS1Padding”,而不是使用提供程序默认值(在当前的Oracle JCE提供程序中返回相同结果,但直接指定它会更安全)。 - Maarten Bodewes

4
您不能仅仅使用“原始”的RSA加密数据而没有任何填充。出于安全原因,您需要一些填充方案。通常使用“RSA/ECB/PKCS1Padding”。这可以加密数据少于密钥大小11个字节。它确保数据适合模数,并添加至少8个字节的随机数据以确保加密例如两次单词"Yes"不会导致两个相同的密码文本。最后,它确保您可以找到加密数据的八位字节大小,因此您可以简单地加密组成AES密钥的16、24或32个字节。

1
当然,您也可以使用后继的OAEP,但这并不是必需的,而且可能比默认的PKCS#1 v1.5填充方案更不兼容。 - Maarten Bodewes

0
需要记住的一件事是,对于RSA和实际上包括AES在内的许多算法,您提供的“有用”数据并不是字面上加密的数据。通常需要包含一些额外的数据,例如指示某种方式下实际数据长度的数据,用于任何完整性检查的数据等等。对于用户来说,输入字节数并不一定等于加密后的字节数。
要获得正确的密钥大小,您可以在密钥上使用HashAlgorithm(SHA),这将为您提供固定的输出大小。否则,您可以只使用前16个字节作为密钥并忽略其余部分?祝好运。

你说得对,看起来它确实是填充到密钥长度(在我的情况下为2048位密钥)。我也没有看到Java 7的Cipher对象支持NOPADDING,但它没有向我抛出异常。我想我会调查一下如何解析出我需要的真实数据。谢谢! - hat

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