我有一个提供应用内计费的Android应用程序,我们有一个应用程序服务器,Android应用程序连接到该服务器为用户提供服务,在应用程序内购买时,我们希望将收据推送到服务器以进行验证过程。
现在的问题是我不知道如何在 dot net(C#) 中转换 Security.java 文件,因为我们的服务器是用 dot net 编写的
注意:此文件附带 Android 应用内计费相同的应用程序,该应用程序提供消息签名功能,我只需要在 .net 中使用它们的等效功能。
有关此问题的更多详细信息,请访问 http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/66bb5683-fde6-47ca-92d7-de255cc8655a
这是一个纯 C# 实现,来自 Checking Google Play Signatures on .Net。
创建一个控制台应用程序项目,将公钥转换为
RSACryptoServiceProvider
期望的 XML 格式。将 PEMKeyLoader.cs 添加到控制台应用程序项目。
using PublicKeyConvert;
using System.Security.Cryptography;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
RSACryptoServiceProvider provider = PEMKeyLoader.CryptoServiceProviderFromPublicKeyInfo(MY_BASE64_PUBLIC_KEY);
System.Console.WriteLine(provider.ToXmlString(false));
}
const string MY_BASE64_PUBLIC_KEY = "Paste your base64 Google public key here.";
}
}
运行该控制台应用程序将输出(到控制台)
RSACryptoServiceProvider
期望的 XML 格式。
现在您已经有了 XML 格式的公钥,您可以使用它来验证签名:
public static bool Verify(string message, string base64Signature, string xmlPublicKey)
{
// Create the provider and load the KEY
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.FromXmlString(xmlPublicKey);
// The signature is supposed to be encoded in base64 and the SHA1 checksum
// of the message is computed against the UTF-8 representation of the message
byte[] signature = System.Convert.FromBase64String(base64Signature);
SHA1Managed sha = new SHA1Managed();
byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
return provider.VerifyData(data, sha, signature);
}
我找到了解决方案,要实现这一点,您首先必须转换公钥格式,因为点网使用不同的密钥作为输入。
我不知道其他方法,但我们可以使用 java 代码获取点网格式密钥,您只需运行一次即可生成点网友好的 RSA 公钥。 (仅当给定的公众不会快速变化时才建议这样做,例如在 Android 市场应用内计费的情况下)
以下 Java 代码对我有用
public static DotNetRSA GenerateDotNetKey(String base64PubKey)
throws IOException, NoSuchAlgorithmException,
InvalidKeySpecException {
/*
* String base64PubKey -
* Is a Key retrieved from Google Checkout Merchant Account
*/
BASE64Decoder decoder = new BASE64Decoder();
byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
byte[] modulusBytes = publicKey.getModulus().toByteArray();
byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();
modulusBytes = stripLeadingZeros(modulusBytes);
BASE64Encoder encoder = new BASE64Encoder();
String modulusB64 = encoder.encode(modulusBytes);
String exponentB64 = encoder.encode(exponentBytes);
return new DotNetRSA(modulusB64, exponentB64);
}
private static byte[] stripLeadingZeros(byte[] a) {
int lastZero = -1;
for (int i = 0; i < a.length; i++) {
if (a[i] == 0) {
lastZero = i;
}
else {
break;
}
}
lastZero++;
byte[] result = new byte[a.length - lastZero];
System.arraycopy(a, lastZero, result, 0, result.length);
return result;
}
现在要验证数字签名,您可以在 dot net 程序(c#)中使用以下代码,前提是 GCHO_PUB_KEY_EXP 是您的指数,GCHO_PUB_KEY_MOD 是上述 Java 代码提取的模数
public static bool VerifyDataSingature(string data, string sign)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
RSAParameters rsaKeyInfo = new RSAParameters()
{
Exponent = Convert.FromBase64String(GCHO_PUB_KEY_EXP),
Modulus = Convert.FromBase64String(GCHO_PUB_KEY_MOD)
};
rsa.ImportParameters(rsaKeyInfo);
return rsa.VerifyData(Encoding.ASCII.GetBytes(data),
"SHA1",
Convert.FromBase64String(sign));
}
}
我希望它对每个人都有效,就像对我一样。谢谢
归功于代码项目文章
对于所有需要验证签名的人来说,这里有一个完整的 C# 实现,使用 BouncyCastle dll 来提供帮助。
如果您向班级提供 Google 公钥,您将能够验证签名,而无需任何 Java 代码。玩得开心。 grtz 马丁
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;
namespace GoogleEncryptTest
{
class GoogleSignatureVerify
{
RSAParameters _rsaKeyInfo;
public GoogleSignatureVerify(String GooglePublicKey)
{
RsaKeyParameters rsaParameters= (RsaKeyParameters) PublicKeyFactory.CreateKey(Convert.FromBase64String(GooglePublicKey));
byte[] rsaExp = rsaParameters.Exponent.ToByteArray();
byte[] Modulus = rsaParameters.Modulus.ToByteArray();
// Microsoft RSAParameters modulo wants leading zero's removed so create new array with leading zero's removed
int Pos = 0;
for (int i=0; i<Modulus.Length; i++)
{
if (Modulus[i] == 0)
{
Pos++;
}
else
{
break;
}
}
byte[] rsaMod = new byte[Modulus.Length-Pos];
Array.Copy(Modulus,Pos,rsaMod,0,Modulus.Length-Pos);
// Fill the Microsoft parameters
_rsaKeyInfo = new RSAParameters()
{
Exponent = rsaExp,
Modulus = rsaMod
};
}
public bool Verify(String Message,String Signature)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(_rsaKeyInfo);
return rsa.VerifyData(Encoding.ASCII.GetBytes(Message), "SHA1", Convert.FromBase64String(Signature));
}
}
}
}
供大家搜索,这里是完整的Java文件,以及VB.NET中的函数。
/**
* <p>Title: RSA Security</p>
* Description: This class generates a RSA private and public key, reinstantiates
* the keys from the corresponding key files.It also generates compatible .Net Public Key,
* which we will read later in C# program using .Net Securtiy Framework
* The reinstantiated keys are used to sign and verify the given data.</p>
*
* @author Shaheryar
* @version 1.0
*/
import java.security.*;
import java.security.spec.*;
import java.io.*;
import java.security.interfaces.*;
import java.security.cert.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class SecurityManager {
private KeyPairGenerator keyGen; //Key pair generator for RSA
private PrivateKey privateKey; // Private Key Class
private PublicKey publicKey; // Public Key Class
private KeyPair keypair; // KeyPair Class
private Signature sign; // Signature, used to sign the data
private String PRIVATE_KEY_FILE; // Private key file.
private String PUBLIC_KEY_FILE; // Public key file.
private String DOT_NET_PUBLIC_KEY_FILE; // File to store .Net Compatible Key Data
/**
* Default Constructor. Instantiates the key paths and signature algorithm.
* @throws IOException
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
public SecurityManager() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
}
public static void main(String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException{
GenerateDotNetKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6340BNzismmb/n98sTcYfNEmmzNGumdWnK1e7NNWntM6mjZMnQaVZ9HiJKmMgtn69dAU4gaMVUWACDsuup1GBxN8dLgDbtR26M0u1jf1G8AQehcKfqxqSYzxKquXXotffdYsJPpjseZbi96Y7j47kz9CjNP3y1BzjJNTWQUx9fc9e2Bpsi0GtqJ8porPBuIGTjcCnlKM14tIv6YlHtECW1L1wcOBkoj/5liI1nhlYDth/DNXg1OY11JqIIP1fO2vQPtKEpdtcTBTjmB9M45O1N8K/shTcMntFjwVTpL0hRd+eaN1bUjpMvrhFik0VcF/ZNN6Hn0Coqe+ey18dLosQIDAQAB");
}
public static void GenerateDotNetKey(String base64PubKey)
throws IOException, NoSuchAlgorithmException,
InvalidKeySpecException {
/*
* String base64PubKey -
* Is a Key retrieved from Google Checkout Merchant Account
*/
BASE64Decoder decoder = new BASE64Decoder();
byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
byte[] modulusBytes = publicKey.getModulus().toByteArray();
byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();
modulusBytes = stripLeadingZeros1(modulusBytes);
BASE64Encoder encoder = new BASE64Encoder();
String modulusB64 = encoder.encode(modulusBytes);
String exponentB64 = encoder.encode(exponentBytes);
int i=0;
// return new DotNetRSA(modulusB64, exponentB64);
}
private static byte[] stripLeadingZeros1(byte[] a) {
int lastZero = -1;
for (int i = 0; i < a.length; i++) {
if (a[i] == 0) {
lastZero = i;
}
else {
break;
}
}
lastZero++;
byte[] result = new byte[a.length - lastZero];
System.arraycopy(a, lastZero, result, 0, result.length);
return result;
}
}
/**
* <p>Title: RSA Security</p>
* Description: This class generates a RSA private and public key, reinstantiates
* the keys from the corresponding key files.It also generates compatible .Net Public Key,
* which we will read later in C# program using .Net Securtiy Framework
* The reinstantiated keys are used to sign and verify the given data.</p>
*
* @author Shaheryar
* @version 1.0
*/
import java.security.*;
import java.security.spec.*;
import java.io.*;
import java.security.interfaces.*;
import java.security.cert.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class SecurityManager {
private KeyPairGenerator keyGen; //Key pair generator for RSA
private PrivateKey privateKey; // Private Key Class
private PublicKey publicKey; // Public Key Class
private KeyPair keypair; // KeyPair Class
private Signature sign; // Signature, used to sign the data
private String PRIVATE_KEY_FILE; // Private key file.
private String PUBLIC_KEY_FILE; // Public key file.
private String DOT_NET_PUBLIC_KEY_FILE; // File to store .Net Compatible Key Data
/**
* Default Constructor. Instantiates the key paths and signature algorithm.
* @throws IOException
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
public SecurityManager() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
}
public static void main(String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException{
GenerateDotNetKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6340BNzismmb/n98sTcYfNEmmzNGumdWnK1e7NNWntM6mjZMnQaVZ9HiJKmMgtn69dAU4gaMVUWACDsuup1GBxN8dLgDbtR26M0u1jf1G8AQehcKfqxqSYzxKquXXotffdYsJPpjseZbi96Y7j47kz9CjNP3y1BzjJNTWQUx9fc9e2Bpsi0GtqJ8porPBuIGTjcCnlKM14tIv6YlHtECW1L1wcOBkoj/5liI1nhlYDth/DNXg1OY11JqIIP1fO2vQPtKEpdtcTBTjmB9M45O1N8K/shTcMntFjwVTpL0hRd+eaN1bUjpMvrhFik0VcF/ZNN6Hn0Coqe+ey18dLosQIDAQAB");
}
public static void GenerateDotNetKey(String base64PubKey)
throws IOException, NoSuchAlgorithmException,
InvalidKeySpecException {
/*
* String base64PubKey -
* Is a Key retrieved from Google Checkout Merchant Account
*/
BASE64Decoder decoder = new BASE64Decoder();
byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);
byte[] modulusBytes = publicKey.getModulus().toByteArray();
byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();
modulusBytes = stripLeadingZeros1(modulusBytes);
BASE64Encoder encoder = new BASE64Encoder();
String modulusB64 = encoder.encode(modulusBytes);
String exponentB64 = encoder.encode(exponentBytes);
int i=0;
// return new DotNetRSA(modulusB64, exponentB64);
}
private static byte[] stripLeadingZeros1(byte[] a) {
int lastZero = -1;
for (int i = 0; i < a.length; i++) {
if (a[i] == 0) {
lastZero = i;
}
else {
break;
}
}
lastZero++;
byte[] result = new byte[a.length - lastZero];
System.arraycopy(a, lastZero, result, 0, result.length);
return result;
}
}
只需添加到一个新的Java项目并作为带有断点(int i = 0;)的java应用程序运行来提取你的密钥,代码不是我的,只是我的,支持原作者,上面的链接
和 VB.NET
我的解决方案基于 BouncyCastle C# nuget。
将消息、签名和密钥替换为您的消息、签名和密钥并进行测试。不需要 java 来获取模数或指数。
Private Function VerifyDataSignature(ByVal data As String, ByVal sign As String) As Boolean
Using rsa As New RSACryptoServiceProvider()
Dim rsaKeyInfo As RSAParameters = New RSAParameters()
rsaKeyInfo.Exponent = Convert.FromBase64String("ExponentFromJava")
rsaKeyInfo.Modulus = Convert.FromBase64String("ModulusFromJava")
rsa.ImportParameters(rsaKeyInfo)
Return rsa.VerifyData(Encoding.ASCII.GetBytes(data), "SHA1", Convert.FromBase64String(sign))
End Using
End Function
对于任何可能仍然感兴趣的人,这里有一个纯 C# 解决方案(使用 .NET 5.0 测试):
[TestMethod]
public void ValidadeMessageTest()
{
//Base64-encoded RSA public key obtained from Google PlayStore, for the app. Go to DevelomentTools->Service & APIs
var GooglePlayPK = "<put your key here>";
bool validateReceipt(String message,String messageSignature)
{
const String SIGNATURE_ALGORITHM = "SHA1";
var rsaParameters = new RSAParameters();
byte[] publicKeyBytes = Convert.FromBase64String(GooglePlayPK);
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
using (var rsa = new RSACryptoServiceProvider())
{
var encoder = new ASCIIEncoding();
byte[] bytesToVerify = encoder.GetBytes(message);
byte[] signedBytes = Convert.FromBase64String(messageSignature);
rsa.ImportParameters(rsaParameters);
return rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID(SIGNATURE_ALGORITHM), signedBytes);
}
}
//test your receipt
Assert.IsTrue(validateReceipt(<original>, <signature>));
}