使用RSA解密字符串会在开头返回额外的零

时间:2016-06-07 15:23:23

标签: java encryption cryptography rsa

我尝试生成AES密钥,对其进行加密并使用RSA对其进行解密。 种类有效,除了在解密数据和使用Base64进行编码之后,我得到了一堆" A"我的实际字符串之前的字母(base64编码的AES密钥)。我猜这些在字节中是零。

" RSA / ECB / NoPadding"参数是强制性的。我究竟做错了什么 ?我需要它来返回原始字符串/字节。

package szyfrator;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.tools.bzip2.CBZip2OutputStream;

import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.sun.org.apache.xml.internal.security.utils.Base64;

public class Cryptography {

    private static byte[] aesKey;
    private static String base64AESKey;
    private static byte[] encryptedAESKey;
    private static String base64AESEncryptedKey;
    private static byte[] aesKeyTransformed;

    public static void main(String args[]){

        Cryptography.generateAESkey();
        Cryptography.encryptAESKey(new File("G:\\HASHBABYHASH\\public.txt"));
        Cryptography.decryptAESKey(new File("G:\\HASHBABYHASH\\private.txt"));

        System.out.println("String: " + Base64.encode(Cryptography.getAesKey()) + "\r\n");
        System.out.println("Encrypted string: " + Cryptography.getBase64EncryptedKey() + "\r\n");
        System.out.println("Decrypted String: " + Base64.encode(Cryptography.getAesKeyTransformed()) + "\r\n");

    }

    public static void generateAESkey(){

        try {
            KeyGenerator    keyGen = KeyGenerator.getInstance("AES");

            keyGen.init(256); 
            SecretKey secretKey = keyGen.generateKey();

            byte[] keyBytes = secretKey.getEncoded(); 
            base64AESKey = Base64.encode(keyBytes); 

            aesKey = keyBytes;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    public static void encryptAESKey(File publicKeyFile){

        try {       
            FileInputStream input = new FileInputStream(publicKeyFile);

            byte[] decoded = Base64.decode(IOUtils.toByteArray(input));     

            X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(decoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(publicSpec);   

            Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);  

            encryptedAESKey = cipher.doFinal(aesKey);
            base64AESEncryptedKey = Base64.encode(encryptedAESKey);

            input.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void decryptAESKey(File privateKeyFile){

        try {
            FileInputStream input = new FileInputStream(privateKeyFile);

            byte[] decoded = Base64.decode(IOUtils.toByteArray(input));

            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

            Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);  

            aesKeyTransformed = cipher.doFinal(encryptedAESKey);
            input.close();  
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果如下:

String: xVwH7Nbz84emVoH0J31sRHC+B669T9wCUVlTDhYgXiI=

Encrypted string: INTA8rx46hX6bZbDIl4iiWsUGO4ywCW0Aee1reqQ3wR5X7He5ztLHvyZoa0WZmUGYbYwprNGffRI
OVJFxczMHkxUfHU1WWCTzcfNylD+sWObIYrbyc13aZi9OL/r1GXuaGtkIgTJyqv0QPHfIri7iaH3
Lr/F4EIcyphJM3E2reQ=

Decrypted String: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxVwH7Nbz84emVoH0J31sRHC+
B669T9wCUVlTDhYgXiI=

1 个答案:

答案 0 :(得分:2)

在RSA中,一些数据被编码为大量数据并进行计算。 NoPadding(未填充或教科书RSA)意味着您对邮件的正确编码负全部责任。所有的计算都是针对大模数进行的(现在至少应该是2048位)。由于Java采用大端数字,因此您的消息会自动编码为最低有效字节,但解密会以相同的模数大小返回已解码的消息,因为它无法知道有意或无的前导零字节。 / p>

为了使此计算正确和安全,必须应用填充。旧式PKCS#1 v1.5填充现在不被认为是安全的,但它只有11个字节的开销(只有2048 / 8-11 = 245个字节可以使用2048位的密钥加密)。较新的PKCS#1 v2.1填充(OAEP)被认为是安全的,应在此处使用。如果使用SHA-1,它的开销为42字节。

  

" RSA / ECB / NoPadding"参数是强制性的。

这真的很糟糕,因为它非常不安全:Which attacks are possible against raw/textbook RSA?

如果您不愿意简单地将密码字符串更改为Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");,则必须自行删除前导零。问题当然是这个"零填充"模式是模糊的,如果明文以0x00字节开头,您将无法将其与填充字节区分开来,并且必须将其删除,从而破坏您的明文。 如果明文是您的情况下的AES密钥,则有0.3%的可能性以0x00字节开头,因此会破坏密钥。您必须确保密钥实际上是正确的如果长度不正确,则填写零字节。

以下是删除前导零字节的方法:

byte[] unpadZeros(byte[] in) {
    int i = 0;
    while(in[i] == 0) i++;
    return Arrays.copyOfRange(in, i, in.length);
}

如果您知道自己正在解密AES密钥,则可以使取消填充不会产生错误的数据:

byte[] unpadZerosToGetAesKey(byte[] in) {
    int i = 0;
    while(in[i] == 0) i++;
    int len = in.length - i;
    if (len <= 16) len = 16;
    else if (len <= 24) len = 24;
    else len = 32;
    return Arrays.copyOfRange(in, in.length - len, in.length);
}