仅使用私钥作为字符串 .NET 使用 RSA SHA 256 对字符串进行签名

问题描述 投票:0回答:2

我们正在通过 RESTful API 与 Google 进行集成,我需要使用 RSA-SHA256 对 JWT 的有效负载进行签名,而我拥有的只是一个字符串形式的私钥,看起来像

-----BEGIN PRIVATE KEY-----
MIIEvgIBfADANBg9qhkdsiG9w0BAQEFAASCBKgw
......
-----END PRIVATE KEY-----

我一直在到处寻找解决方案,但每个人都在谈论

X509Certificate2
,它需要 p12 文件或一些证书。现在我没有这些,我只有一个字符串,即私钥。

Google 推荐 https://jwt.io/ 并且它可以与密钥字符串一起使用,但是,我需要我的代码才能做到这一点。页面中列出了 3 个库:

  1. 其中一个属于 Microsoft,完全没有文档。
  2. 另一种使用
    X509Certificate2
    或专门用于字符串键,只有到此位置的链接 http://www.donaldsbaconbytes.com/2016/08/create-jwt-with-a-private-rsa-key/它使用了一些我没有的证书文件,使其成为一个错误且无用的链接。
  3. #2 的同一创建者还有另一个库,但它是针对手机的,它实际上有一种 RSA256 签名方法,该方法将私钥作为与我的格式完全相同的字符串。

所以我的问题是 - 对于我的情况有一些可行的解决方案吗?因为我不敢相信没有人与 Google 集成。

c# jwt rsa sha256 sign
2个回答
2
投票

我知道一种将其转换为可以在 C# 中使用的 pfx 的方法。

首先,如果尚未安装 OpenSSL,请安装。 (您可以从 http://slproweb.com/products/Win32OpenSSL.html 获取最新的“Win64 OpenSSL v#.#.#x Light”,但我对此来源不承担任何责任。)

您可能需要将“C:\Program Files\OpenSSL in\”添加到您帐户的路径变量中,以便可以从命令行中的任何位置使用“openssl.exe”。

在命令窗口中,导航到您的私钥文件所在的位置。我们称之为“priv.key”。发出以下两个命令,忽略注释:

# Create public self-signed certificate CRT file (with 7000yr validity) from private key (may leave all info empty)
openssl req -x509 -sha256 -new -key priv.pem -out pub.crt -days 2555000

# Combine into PFX container (encrypted with AES-256) (certificate needs to be included for .NET to read successfully)
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx

请注意,在最后一个命令中,我正在创建一个加密的 pfx(这是可选的 - 我相信您可以将

-aes256
替换为
-nodes
来跳过它),并且我正在配置加密提供程序,以便可以使用密钥对于 SHA256 操作,没有 C# 抱怨。

您现在拥有一个 pfx 文件,可以使用

X509Certificate2
的构造函数加载该文件。

我倾向于使用此备忘单来执行常见的 OpenSSL 命令: https://www.sslshopper.com/article-most-common-openssl-commands.html


0
投票

我花了一整天的时间来写这段代码。抱歉,我没有 gitHub,所以我会在这里分享。这包括一切,包括使用 SHA256withRSA 签名和编码等。

我在 Google 上设置了一个服务帐户并下载了私钥。

在.Net8上与Google Oauth2.0完全C#集成 ------------ 我希望这对某人有帮助!

    //guide here https://developers.google.com/identity/protocols/oauth2/service-account#httprest_1

namespace Scraper2.HttpServices
{
    public class CorpusService : ICorpusService
    {
        //private readonly HttpClientHandler _handler;
        private readonly ILogger<CensusGeocoderService> _logger;
        private readonly HttpClient _client;
        private readonly IConfiguration _configuration;

        public CorpusService(
            HttpClient client,
            ILogger<CensusGeocoderService> logger,
            IConfiguration configuration
            )
        {
            _client = client;
            _logger = logger;
            _configuration = configuration;
        }

        private HttpRequestMessage buildHttpRequest()
        {
            //RS256 for RSA256 hash algorithm
            string jwt_header = "{\"alg\":\"RS256\",\"typ\":\"JWT\", \"kid\":\"" +
                _configuration.GetValue<string>("google_service_account_key:private_key_id") + //"370ab79b4513eb9bad7c9bd16a95cb76b5b2a56a" +
                "\"}";
            string base64header = jwt_header.Base64Encode();
            string claims = "{" +
                "\"iss\":" + "\"" + _configuration.GetValue<string>("google_service_account_key:client_email") + "\"," +
                "\"scope\":" + "\"https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/generative-language.retriever\"," +
                "\"aud\":" + "\"https://oauth2.googleapis.com/token\"," +

                //seconds since 00:00 Jan 1 1970 UTC plus 1 hr
                "\"exp\":" + Math.Floor((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds + 3600) + "," +
                "\"iat\":" + Math.Floor((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds) +
                "}";

            /*
             * 
iss     The email address of the service account.
scope   A space-delimited list of the permissions that the application requests.
aud     A descriptor of the intended target of the assertion. When making an access token request this value is always https://oauth2.googleapis.com/token.
exp     The expiration time of the assertion, specified as seconds since 00:00:00 UTC, January 1, 1970. This value has a maximum of 1 hour after the issued time.
iat     The time the assertion was issued, specified as seconds since 00:00:00 UTC, January 1, 1970.
             */

            string base64claims = claims.Base64Encode();

            //input is base64encoded header + . + base64encoded claim set
            byte[] inputBytes = Encoding.UTF8.GetBytes(base64header + "." + base64claims);

            

            //sign it with sha256 with RSA private key
            byte[] signature = StringExtend.sha256withrsa(
                private_key: _configuration.GetValue<string>("google_service_account_key:private_key"),
                data_to_sign: inputBytes);

            string base64signature = Convert.ToBase64String(signature);

            string completed_jwt = base64header + "." +
                base64claims + "." +
                base64signature;

            Debug.WriteLine("completed jwt: " + completed_jwt);

            string uri = "https://oauth2.googleapis.com/token";

            Dictionary<string, string> parameters = new Dictionary<string, string>
            {
                { "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" },//WebUtility.UrlEncode()
                { "assertion", completed_jwt }
            };

            return new HttpRequestMessage()
            {
                RequestUri = new Uri(uri),
                Method = HttpMethod.Post,
                Content = new FormUrlEncodedContent(parameters)
                /*
grant_type  Use the following string, URL-encoded as necessary: urn:ietf:params:oauth:grant-type:jwt-bearer
assertion   The JWT, including signature.
                 */
                
            };
            
        }

        private async Task<string> sendRequest(HttpRequestMessage request)
        {
            //Dictionary<string, double> lat_and_lon = new Dictionary<string, double>();
            //yelpEntry result = new yelpEntry();
            //string[] willCauseErrors = { "&", "*", "\\", "/" };
            //HttpRequestMessage request = await buildHTTPRequest(userQuery);

            Debug.WriteLine("Getting Google access token at {0}",
                request.RequestUri
                );
            var response = await _client.SendAsync(request);
            Debug.WriteLine("response: " + response);

            //if (response.IsSuccessStatusCode)
            //{
            //response.EnsureSuccessStatusCode();
            //return response;
            Stream responseStream = await response.Content.ReadAsStreamAsync();
            StreamReader reader = new StreamReader(responseStream);
            return reader.ReadToEnd();

        }

        /*
         * {
     "access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
     "scope": "https://www.googleapis.com/auth/prediction"
     "token_type": "Bearer",
     "expires_in": 3600
   }
         */

        //main method to bring it all together
        public async Task<string> corpus_service_main()
        {
            HttpRequestMessage request = buildHttpRequest();

            return await sendRequest(request);
            

        }


    }
}

其他方法:

    public static string Base64Encode(this string plainText)
{
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    return System.Convert.ToBase64String(plainTextBytes);
}

public static byte[] sha256withrsa (string private_key, byte[] data_to_sign)
{
    //using (RSA rsa = RSA.Create())
    //{
        //convert the string private key to byte[], then use it to sign. 
        //rsa.ImportPkcs8PrivateKey(Encoding.UTF8.GetBytes(private_key), out _);
        
        RSA rsa = RSA.Create();
        rsa.ImportFromPem(private_key);
        return rsa.SignData(data_to_sign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
   // }
}
© www.soinside.com 2019 - 2024. All rights reserved.