如何在 Android 上使用 Java BouncyCastle API 对带有明文密钥的字符串进行 RSA 加密
我正在尝试使用 Android 中的 BouncyCastle API 加密字符串以发送到服务器。
我有明文形式的公钥(当然,在内存中,而不是在文件系统中!密码学家们,无需对我大喊大叫;)),并且我需要使用此明文公钥将字符串加密为 RSA 加密字符串。
这是我的课程:
public class RSAEncryptor {
//Get certificate from base64 string
public static X509Certificate getCertificateFromBase64String(String string)
throws CertificateException, javax.security.cert.CertificateException
{
if(string.equals("") || string == null) {
return null;
}
byte[] certBytes = Base64.decode(string.getBytes(), Base64.DEFAULT);
X509Certificate cert = X509Certificate.getInstance(certBytes);
return cert;
}
//Get public key from base64 encoded string
public static PublicKey getPublicKeyFromEncodedCertData(String encodedCertData)
throws CertificateException, javax.security.cert.CertificateException
{
if(encodedCertData == null || encodedCertData.equals("")) return null;
X509Certificate cert = getCertificateFromBase64String(encodedCertData);
if(cert == null) return null;
return cert.getPublicKey();
}
public static String rsaEncrypt(String plainText, String keyFromResources)
throws NoSuchAlgorithmException, InvalidKeySpecException,
IOException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException //
{
if(plainText == null || plainText.equals("")) return null;
if(keyFromResources == null || keyFromResources.equals("")) return null;
byte[] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = null;
try {
publicKey = getPublicKeyFromEncodedCertData(keyFromResources);
}
catch(Exception ex) {
Logger.LogError("getPublicKeyFromEncodedCertData()", ex);
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainText.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
}
我目前没有得到正确加密的字符串,只是一团乱码,如下所示:
��RB��%����I��Q��F*�bd[ @�y�_H]T{KƾuTN�Q� ��U�f��]�S �q|.t�t�9�Rˇ�����)��{�},系数#��@k=�WO���f�7t"yP�z�
( 是空字符)
我应该返回一个类似于以下内容的字母数字字符串:
2+tSXez8JrAIX+VJ2Dy4IsA56XhWpTwF8X2yGGaI6novucXknwykDyqJZICpmYcqx75qBRgxwrW2kY9LmQR2xU 17PLqTukAu2Bna8WXYTmJJQ7CWsN3SdABlETRfsYA+g3A2rO2Qp6aR9OCBcFVJpnZJjb9kaOUj5Pcj0tNPFdM= (显然与实际响应有所不同:D)
我希望得到任何帮助!
谢谢!
以前有人这样做过吗?我希望您能提供有关如何解决此问题的建议。
I am trying to encrypt a string using the BouncyCastle API in Android to send off to a server.
I have the public key in plaintext (in memory, not in the filesystem, of course! no need to yell at me, cryptographers ;) ) and I need to use this plaintext public key to encrypt a string to an RSA encrypted string.
This is my class:
public class RSAEncryptor {
//Get certificate from base64 string
public static X509Certificate getCertificateFromBase64String(String string)
throws CertificateException, javax.security.cert.CertificateException
{
if(string.equals("") || string == null) {
return null;
}
byte[] certBytes = Base64.decode(string.getBytes(), Base64.DEFAULT);
X509Certificate cert = X509Certificate.getInstance(certBytes);
return cert;
}
//Get public key from base64 encoded string
public static PublicKey getPublicKeyFromEncodedCertData(String encodedCertData)
throws CertificateException, javax.security.cert.CertificateException
{
if(encodedCertData == null || encodedCertData.equals("")) return null;
X509Certificate cert = getCertificateFromBase64String(encodedCertData);
if(cert == null) return null;
return cert.getPublicKey();
}
public static String rsaEncrypt(String plainText, String keyFromResources)
throws NoSuchAlgorithmException, InvalidKeySpecException,
IOException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException //
{
if(plainText == null || plainText.equals("")) return null;
if(keyFromResources == null || keyFromResources.equals("")) return null;
byte[] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = null;
try {
publicKey = getPublicKeyFromEncodedCertData(keyFromResources);
}
catch(Exception ex) {
Logger.LogError("getPublicKeyFromEncodedCertData()", ex);
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainText.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
}
I'm currently not getting the properly encrypted string back out, just a garbled mess like this:
��RB��%����I��Q��F*�bd[@�y�_H]T{KƾuTN�Q�
��U�f��]�S
�q|.t�t�9�Rˇ�����)��{�},ޱ�ª�ǥ#���@k=�WO���f�7t"yP�z�
(The <?>
's are null chars)
I should be getting back an alphanumeric string similar to this:
2+tSXez8JrAIX+VJ2Dy4IsA56XhWpTwF8X2yGGaI6novucXknwykDyqJZICpmYcqx75qBRgxwrW2kY9LmQR2xU17PLqTukAu2Bna8WXYTmJJQ7CWsN3SdABlETRfsYA+g3A2rO2Qp6aR9OCBcFVJpnZJjb9kaOUj5Pcj0tNPFdM=
(changed obviously from the actual response :D)
I'd appreciate any help!
Thanks!
Has anyone done this before? I'd love any suggestions you have as to how to fix this.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
代码中的问题:
坏主意!
Cipher#doFinal
返回一个byte[]
是有充分理由的。它看起来像随机数据 - 将其转换为字符串肯定会造成混乱,因为平台默认编码(大多数情况下为 UTF-8)几乎肯定会错误地解释随机字节。因此,如果您想从加密数据中获得字符串,那么您应该对字节数组进行 Base64 或 Hex 编码。根据你所说的你所期望的,我会说你想要 Base64 编码的数据,所以你应该对 Cipher 的输出进行 Base64 编码。
然后,加密字符串(这是真实的、人类可读的文本吗?)也不是最佳的。高度脆弱、熵减少加上 ECB 模式(由 RSA 密码使用)的特性会大大降低解决方案的安全性。
普通 RSA 密码绝不能用于加密大于一个块(即大于 RSA 密钥的密钥大小)的数据,并且仅当数据是加密安全随机数据时才使用。在 99% 的所有情况下,这仅适用于用于对称密码(例如 AES 等)的对称密钥。
使用 RSA 除了对称密钥包装和数字签名之外别无其他,在您想要实际加密敏感数据的所有其余情况下,您可以使用作为对称密码,AES 是一个不错的选择 - 128 位或 256 位并不重要。
工作流程将如下所示:
生成 AES 的对称密钥(如果使用 AES-128/256,则为 16/32 字节)。现在,您将使用服务器的公钥对该对称密钥进行 RSA 加密,并将密钥发送到服务器,然后使用 AES 和对称密钥加密您的私有数据,服务器将解密对称密钥使用其私有 RSA 密钥并解密您发送给它的数据包。
使用 TLS:
请注意我使用 would。以上只是故事的一部分。您刚刚发明的是密钥传输协议。除非您是为了生存而设计的,否则您在第一次尝试时就无法获得如此安全的可能性很高(如中间人攻击、反射攻击、重放攻击……)。
这就是为什么我认为在客户端设备和服务器之间建立安全通道的唯一广泛可用的安全选项是使用 TLS(以前的 SSL)。该协议是专门为通过单向(仅服务器)或双向身份验证(客户端和服务器)交换私有数据而设计的(身份验证是在您的情况下使用 RSA 的部分 - 配置“服务器证书”) ”)。
它已经经过多年的强化和修改了几次,以抵御对此类协议的所有已知攻击。我知道,每隔一天就会有关于“SSL”如何被这个或那个人破坏的消息,但是,如果您仔细设置它,对于没有丰富协议设计经验的凡人来说,它仍然是安全的。
好处是您只需在服务器上配置它(与从头开始发明协议相比,这相当容易),就能够在客户端和服务器之间使用完全加密的安全通信。如果您在从“公共 CA”购买的服务器上设置了证书/密钥对,那么使用 TSL 对您的客户端来说是完全透明的 - 他们只需将其访问 URL 从“http”更改为“https” - 服务器的证书将通过能够在证书路径中识别它来自动信任它,该路径通向保存在 Java 的默认信任存储
cacerts
中的根证书之一。Problems in the code:
Bad idea!
Cipher#doFinal
returns abyte[]
for a good reason. It looks like random data - turning this into a String will make a mess for sure, because the platform default encoding (UTF-8 in most cases) will interpret the random bytes wrong almost with certainty. So if you want to have a String from encrypted data, then you should Base64- or Hex-encode the byte array.From what you said you were expecting I would say you want Base64-encoded data, so you should Base64-encode your Cipher's output.
Then, encrypting a string (is this real, human-readable text?) is also less than optimal. Highly vulnerable, the reduced entropy plus the characteristics of the ECB mode (this is used by the RSA cipher) lower the security of your solution drastically.
A plain RSA cipher should never be used to encrypt data that is larger than one block (i.e. larger than the key size of your RSA key) and also only if the data is cryptographically secure random. In 99% of all cases this is only given for symmetric keys used for symmetric Ciphers such as AES etc.
Use RSA for nothing else than symmetric key wrapping and digital signatures, in all remaining cases where you want to actually encrypt sensitive data, you use a symmetric cipher, AES is a good choice - 128 or 256 bits doesn't really matter.
The workflow would look like this:
Generate a symmetric key for AES (16/32 bytes if you use AES-128/256). Now you would RSA-encrypt this symmetric key and nothing else using the server's public key and send the key to the server, then encrypt your private data using AES and the symmetric key, the server would decrypt the symmetric key using its private RSA key and decrypt the packets you send to it.
Use TLS:
Note my use of would. The above is only part of the story. What you just invented there is a Key Transport Protocol. And unless you are designing those for a living chances are high that you won't get this secure on your first try (as in Man-In-The-Middle-Attacks, Reflection Attacks, Replay Attacks, ...).
That's why in my opinion the only widely available secure option to set up a secure channel between client device and server is to use TLS (the former SSL). The protocol was designed specifically for the purpose of exchanging private data with one-way (server only) or two-way authentication (client and server) (authentication is the part where you would use RSA for in your case - configuring the "server certificate").
It has been hardened for years and revised a couple of times to withstand all known attacks on such protocols. And I know, there are messages every other day about how "SSL" has been broken by this or that person, but still, if you set it up carefully it's as secure as it gets for mere mortals without extensive experience in protocol design.
And the nice thing is you only have to configure it on the server (which is quite easy compared to inventing a protocol from scratch) to be able to use fully encrypted, secure communication between both client and server. If you set up a certificate/key pair on the server that was bought from a "public CA" then using TSL is completely transparent to your clients - they merely change their access URL from 'http' to 'https' - the server's certificate will be automatically trusted by being able to identify it in a certificate path that leads to one of the root certificates kept in Java's default trust store
cacerts
.