在 Android 设备上验证 C# 生成的数字签名
我正在编写一个 Android 应用程序,想要验证 C# 程序生成的字符串是否真实(即由我编写的另一个应用程序生成)。为此,我对字符串进行签名,并将字符串和字符串的哈希值传输到 Android 设备。我可以在 Android 应用程序中包含用于创建散列字符串的公钥,这样我就不必传输它。生成所有这些内容的 C# 代码类似于以下内容:
bytes = ASCIIEncoding.ASCII.GetBytes(someString);
provider = new RSACryptoServiceProvider();
signature = provider.SignData(bytes, new SHA1CryptoServiceProvider());
keyParameters = cryptoProvider.ExportParameters(false);
为了传输数据,我使用以下方法将签名编码为 base64:
signatureString = System.Convert.ToBase64String(signature);
我从关键参数中手动提取了模数和公共指数。它们似乎都已进行 Base64 编码。在 android 应用程序中,我尝试按如下方式验证签名:
String modulus = "<modulus string here...>";
String exponent = "<exponent string here...>";
BigInteger BIModulus = new BigInteger(1, Base64.decode(modulus));
BigInteger BIExponent = new BigInteger(1, Base64.decode(exponent));
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(BIModulus, BIExponent);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decodedBytes = cipher.doFinal(Base64.decode(signature));
当我将decodedBytes 与原始字符串的 SHA1 哈希值进行比较时,它们是不同的。为了找出原因,我使用了大量的日志语句逐步完成了整个过程。模数的 BigInteger 似乎与 C# 代码使用的模数不同。指数也会发生同样的情况。我怀疑这是因为Java的字节类型是无符号的?
所以,有两个问题:
1)我做错了什么(或没有做错)?有没有办法手动将模数和指数从一个设备移动到另一个设备以验证签名?
2)有没有一种方法可以在更合适的抽象级别上实现我的目标?
I'm writing an Android application that would like to verify that a string produced by a C# program is authentic (i.e. produced by another application that I've written). To do that, I'm signing the string and transmitting the string and the hash of the string to the Android device. I can include the public key used to create the hashed string in the Android application so I don't have to transmit it. The C# code that produces all of that is akin to what follows:
bytes = ASCIIEncoding.ASCII.GetBytes(someString);
provider = new RSACryptoServiceProvider();
signature = provider.SignData(bytes, new SHA1CryptoServiceProvider());
keyParameters = cryptoProvider.ExportParameters(false);
To transmit the data, I encode the signature as base64 using:
signatureString = System.Convert.ToBase64String(signature);
I manually extracted the modulus and the public exponent from the key Parameters. They appear to both be Base64 encoded already. In the android application I'm attempting to verify the signature as follows:
String modulus = "<modulus string here...>";
String exponent = "<exponent string here...>";
BigInteger BIModulus = new BigInteger(1, Base64.decode(modulus));
BigInteger BIExponent = new BigInteger(1, Base64.decode(exponent));
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(BIModulus, BIExponent);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decodedBytes = cipher.doFinal(Base64.decode(signature));
When I compare decodedBytes
to the SHA1 hash of the original string, they're different. Trying to figure out why, I stepped through the process with oodles of logging statements. It appears that the BigInteger for the modulus is different from the modulus used by the C# code. The same happens with the exponent. My suspicion is that this is because Java's byte type is unsigned?
So, two questions:
1) What am I doing (or not doing) wrong? Is there a way to manually move the modulus and exponent from one device to another to verify a signature?
2) Is there a way to accomplish what I'm aiming for that works at a more appropriate level of abstraction?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
问题#1:
您想要验证签名,因此不应使用
Cipher
类,而应使用Signature
类。注意:
SHA1withRSA
算法使用 RSA 和 SHA-1,如 PKCS#1 标准中所述。我不是 .net 专家,我不知道RSACryptoServiceProvider.SignData(...)
是否使用此标准。大多数情况下,签名密钥都是通过证书传输的,该证书绑定了公钥(模数+指数) RSA 案例)到一个实体(密钥的所有者,可以是一个人、一台计算机、一家公司...)
问题#2:
这绝对取决于您的用例:为什么您需要签名(身份验证、完整性)?你签什么?
更新:
使用简单的 RSA 签名,只要与正确的方案/协议一起使用(例如 PKCS#1,到处实现,至少是 1.5 版本),没问题。另一种解决方案是使用消息身份验证代码(例如HMAC),它提供身份验证和完整性服务,并使用对称密钥。
要携带公钥,您可以在验证应用程序中对密钥进行硬编码(或使用自签名证书)。您必须注意,如果私钥被泄露、丢失或损坏,您将必须更新所有客户端。第二种解决方案是使用 PKI 颁发签名证书;该解决方案支持密钥撤销和续订,但有几个缺点:设置和维护不太容易,根据您的需求和环境,可能有点矫枉过正。
Question #1:
You want to verify a signature therefore you should not use the
Cipher
class but theSignature
class.Note:
SHA1withRSA
algorithm uses RSA with SHA-1 as described in the PKCS#1 standard. I am not a .net expert and I don't know if theRSACryptoServiceProvider.SignData(...)
uses this standard.Most of the time signature keys are transfered with certificates which binds a public key (modulus + exponent in the RSA case) to an entity (the owner of the key, can be a person, a computer, a company...)
Question #2:
Definitely it depends on your use case: why do you need signature (authentication, integrity)? what are you signing?
UPDATE:
Using a simple RSA signature, as far as used with a proper scheme/protocol (e.g. PKCS#1, implemented everywhere, at least the 1.5 version), is ok. Another solution is using a Message Authentication Code (like HMAC) which provides authentication and integrity service and is using symmetric keys.
To carry the public key you can either hardcode the key (or use a self-signed certificate) in your verifying application. You must be aware that if the private key is compromised, lost or destroyed you will have to update all clients. A second solution is using a PKI issuing a signing certificate; this solution supports key revocation and renewal but has several drawbacks: not so easy to setup and maintain, may be an overkill depending on your needs and environment.