安卓中的RSA加密解密

50

我正在Android中实现RSA加密和解密的演示。我可以很好地执行加密,但是在解密时出现异常:>>java.security.InvalidKeyException: unknown key type passed to RSA

    KeyPairGenerator kpg;
    KeyPair kp;
    PublicKey publicKey;
    PrivateKey privateKey;
    byte [] encryptedBytes,decryptedBytes;
    Cipher cipher,cipher1;
    String encrypted,decrypted;

    public String RSAEncrypt (final String plain) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException 
    {
        kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(1024);
        kp = kpg.genKeyPair();
        publicKey = kp.getPublic();
        privateKey = kp.getPrivate();

        cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        encryptedBytes = cipher.doFinal(plain.getBytes());
        encrypted = new String(encryptedBytes);
        System.out.println("EEncrypted?????"+encrypted);
        return encrypted;

    }

    public String RSADecrypt (final String result) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException 
    {

        cipher1=Cipher.getInstance("RSA");
        cipher1.init(Cipher.DECRYPT_MODE, privateKey);
        decryptedBytes = cipher1.doFinal(result.getBytes());
        decrypted = new String(decryptedBytes);
        System.out.println("DDecrypted?????"+decrypted);
        return decrypted;

    }

我在这里调用了这个函数:

encrypt.setOnClickListener(new OnClickListener()
        { 
            public void onClick(View arg0) 
            {
                    try
                    {
                        RSAEncrypt rsaencrypt=new RSAEncrypt();
                        rsaencrypt.RSAEncrypt(name);

                        result=rsaencrypt.RSAEncrypt(name);
                        Toast.makeText(getBaseContext(), result.toString(),Toast.LENGTH_SHORT).show();

                        System.out.println("Result:"+result);
                    }
                    catch(Exception e)
                    {
                        e.printStackTrace();
                        Toast.makeText(getBaseContext(), e.toString(),Toast.LENGTH_LONG).show();
                    }
            }
        });

        decrypt.setOnClickListener(new OnClickListener()
        { 
            public void onClick(View arg0) 
            {
                {
                    try
                    {
                        RSAEncrypt rsadecrypt=new RSAEncrypt();

                        rsadecrypt.RSADecrypt(result);

                        ans=rsadecrypt.RSADecrypt(result);
                        System.out.println("Result is"+ans);
                        Toast.makeText(getBaseContext(), ans.toString(),Toast.LENGTH_LONG).show();
                    }
                    catch(Exception e)
                    {
                        e.printStackTrace();
                        Toast.makeText(getBaseContext(), e.toString(),Toast.LENGTH_LONG).show();
                        System.out.println("Exception is>>"+e);
                    }
            }
        });
5个回答

51

在RSA中,您应该使用公钥进行加密,使用私钥进行解密。

您的示例代码使用公钥进行加密和解密 - 这是无法正常工作的。

因此,在解密部分,您应该以以下方式初始化密码:

cipher1.init(Cipher.DECRYPT_MODE, privateKey);

此外,您的代码有第二个重大错误:

您正在将具有二进制内容的字节数组转换为字符串。

绝对不要将二进制数据转换为字符串!

字符串是用于字符串字符的,而不是二进制数据。如果您想将二进制数据打包到字符串中,请将其编码为可打印字符,例如使用十六进制或Base64。

以下示例使用org.apache.common.codec包的十六进制编码器 - 这是一个必须安装的第三方库。

public byte[] RSAEncrypt(final String plain) throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
    kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(2048);
    kp = kpg.genKeyPair();
    publicKey = kp.getPublic();
    privateKey = kp.getPrivate();

    cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    encryptedBytes = cipher.doFinal(plain.getBytes());
    System.out.println("EEncrypted?????" + new String(org.apache.commons.codec.binary.Hex.encodeHex(encryptedBytes)));
    return encryptedBytes;
}

public String RSADecrypt(final byte[] encryptedBytes) throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

    cipher1 = Cipher.getInstance("RSA");
    cipher1.init(Cipher.DECRYPT_MODE, privateKey);
    decryptedBytes = cipher1.doFinal(encryptedBytes);
    decrypted = new String(decryptedBytes);
    System.out.println("DDecrypted?????" + decrypted);
    return decrypted;
}

非常感谢您。您是正确的。我在那里犯了错误。但现在我遇到了异常>> System.err(416): java.security.InvalidKeyException: 传递给RSA的未知密钥类型。请帮帮我。提前致谢。 - Riddhi Barbhaya
3
谢谢您的反馈。只有一个注释:密钥是对称的。因此,您可以使用公钥进行编码,然后使用私钥进行解码(请确保将加密数据发送给谁),或者使用私钥进行编码,然后使用公钥进行解码(以确保是谁向您发送了加密数据)。使用两个不同的密钥集进行这两种操作,可以同时强制执行“来自谁”和“发往谁”的安全性方面。 - Pascal
3
“使用私钥进行编码”的情况被称为签名。但您永远不应直接对数据进行签名 - 只需要对哈希进行签名,否则可能会遭受某些攻击(请参阅IT安全文献)。 - Robert
只是为了术语准确 [机密性:使用公钥加密,使用私钥解密] [签名:使用私钥签名,使用公钥验证] - 这正确吗? - Franklin
@Franklin 正确。请记住,通常情况下你不会使用非对称加密来加密大量数据,仅用于加密生成的对称密钥(例如 AES 密钥)。 - Robert
显示剩余5条评论

14

我的班级:

package com.infovale.cripto;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;

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

public class RSA {

KeyPairGenerator kpg;
KeyPair kp;
PublicKey publicKey;
PrivateKey privateKey;
byte[] encryptedBytes, decryptedBytes;
Cipher cipher, cipher1;
String encrypted, decrypted;

public String Encrypt (String plain) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException 
{
    kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(1024);
    kp = kpg.genKeyPair();
    publicKey = kp.getPublic();
    privateKey = kp.getPrivate();

    cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    encryptedBytes = cipher.doFinal(plain.getBytes());

    encrypted = bytesToString(encryptedBytes);
    return encrypted;

}

public String Decrypt (String result) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException 
{           

    cipher1=Cipher.getInstance("RSA");
    cipher1.init(Cipher.DECRYPT_MODE, privateKey);
    decryptedBytes = cipher1.doFinal(stringToBytes(result));
    decrypted = new String(decryptedBytes);
    return decrypted;

}

public  String bytesToString(byte[] b) {
    byte[] b2 = new byte[b.length + 1];
    b2[0] = 1;
    System.arraycopy(b, 0, b2, 1, b.length);
    return new BigInteger(b2).toString(36);
}

public  byte[] stringToBytes(String s) {
    byte[] b2 = new BigInteger(s, 36).toByteArray();
    return Arrays.copyOfRange(b2, 1, b2.length);
}
}

14

这里是Android的示例:

  • 生成私钥/公钥RSA密钥对
  • 加密字符串
  • 解密加密后的字符串

这些方法处理了所有的base64编码/解码。

    public void TestEncryptData(String dataToEncrypt) {
        // generate a new public/private key pair to test with (note. you should only do this once and keep them!)
        KeyPair kp = getKeyPair();

        PublicKey publicKey = kp.getPublic();
        byte[] publicKeyBytes = publicKey.getEncoded();
        String publicKeyBytesBase64 = new String(Base64.encode(publicKeyBytes, Base64.DEFAULT));

        PrivateKey privateKey = kp.getPrivate();
        byte[] privateKeyBytes = privateKey.getEncoded();
        String privateKeyBytesBase64 = new String(Base64.encode(privateKeyBytes, Base64.DEFAULT));

        // test encryption
        String encrypted = encryptRSAToString(dataToEncrypt, publicKeyBytesBase64);

        // test decryption
        String decrypted = decryptRSAToString(encrypted, privateKeyBytesBase64);
    }

    public static KeyPair getKeyPair() {
        KeyPair kp = null;
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(2048);
            kp = kpg.generateKeyPair();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return kp;
    }

    public static String encryptRSAToString(String clearText, String publicKey) {
        String encryptedBase64 = "";
        try {
            KeyFactory keyFac = KeyFactory.getInstance("RSA");
            KeySpec keySpec = new X509EncodedKeySpec(Base64.decode(publicKey.trim().getBytes(), Base64.DEFAULT));
            Key key = keyFac.generatePublic(keySpec);

            // get an RSA cipher object and print the provider
            final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
            // encrypt the plain text using the public key
            cipher.init(Cipher.ENCRYPT_MODE, key);

            byte[] encryptedBytes = cipher.doFinal(clearText.getBytes("UTF-8"));
            encryptedBase64 = new String(Base64.encode(encryptedBytes, Base64.DEFAULT));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return encryptedBase64.replaceAll("(\\r|\\n)", "");
    }

    public static String decryptRSAToString(String encryptedBase64, String privateKey) {

        String decryptedString = "";
        try {
            KeyFactory keyFac = KeyFactory.getInstance("RSA");
            KeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey.trim().getBytes(), Base64.DEFAULT));
            Key key = keyFac.generatePrivate(keySpec);

            // get an RSA cipher object and print the provider
            final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
            // encrypt the plain text using the public key
            cipher.init(Cipher.DECRYPT_MODE, key);

            byte[] encryptedBytes = Base64.decode(encryptedBase64, Base64.DEFAULT);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            decryptedString = new String(decryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return decryptedString;
    }

1

我认为问题在于您应该使用相同的密钥对来加密和解密密码。参考JavaDoc:

 genKeyPair() This will generate a new key pair every time it is called.

@jaredzhang,我编辑了我的代码,但是我仍然得到相同的异常,请审核我的编辑后的代码。 - Riddhi Barbhaya
@jaredzhang,我想你是对的。但问题是我现在遇到了java.lang.NullPointerException错误。 - Riddhi Barbhaya
你的新代码似乎不太清晰,请确保你用于解密的私钥与之前生成的私钥相同。 - jaredzhang
@jaredzhang 你好,我又修改了我的代码,希望现在更加清晰明了。请帮忙看一下,非常感谢。同时也请看一下我的问题,我现在得到的异常信息也有所不同。 - Riddhi Barbhaya
@jaredzhang 所以这不能用于将数据从一个应用程序发送到另一个应用程序,因为接收方应用程序将调用genKeyPair()并创建不同的密钥。有什么解决办法吗? - Vir Rajpurohit
@VirRajpurohit 我认为我们应该将密钥保存在Android密钥库中。 - Ankit Verma

0
当使用RSAEcvypt方法时,它会填充公钥和私钥。 当您解密生成的字节数组时,您的公钥和私钥为空。 因此,您会收到此错误。
您应该使用静态密钥;
enter code here

KeyPairGenerator kpg;
KeyPair kp;
static PublicKey publicKey;
static PrivateKey privateKey;
byte [] encryptedBytes,decryptedBytes;
Cipher cipher,cipher1;
String encrypted,decrypted;

那些是类属性,因为访问这些键的方法不是静态的,所以属性不需要是静态的。实际上,拥有静态属性是非常糟糕的选择,特别是当您可能需要创建具有不同数据值的多个类实例时。 - AaA

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