在 C# 中使用 json 私钥对 Header 和 Payload 进行签名

Posted

技术标签:

【中文标题】在 C# 中使用 json 私钥对 Header 和 Payload 进行签名【英文标题】:Signing the Header and Payload with json private key in C# 【发布时间】:2021-11-20 08:59:41 【问题描述】:

我必须生成一个使用 json 私钥签名的客户端断言字符串。

我在 private.json 文件中有私钥。如何使用文件中的私钥对 Header 和 payload 进行签名?

除了签名部分,我已经设法让其他事情发挥作用。以下是我用于获取签名客户端断言字符串的代码

 public string GetSignedClientAssertion()

    var header = new Dictionary<string, string>()
     
           "typ" , "JWT",
           "alg", "ES512",
           "kid", "TST_Staging" 
     ;
        
    string token = Encode(Encoding.UTF8.GetBytes(JObject.FromObject(header).ToString())) + "." + Encode(Encoding.UTF8.GetBytes(JObject.FromObject(GetClaims()).ToString()));
     var rsa = new RSACryptoServiceProvider();
       var filename = System.Web.HttpContext.Current.Server.MapPath("Private.json");
         string data = File.ReadAllText(filename);
    string key = Encode(Encoding.UTF8.GetBytes(data));
    byte[] databyte = File.ReadAllBytes(filename);
 
    //-----I am stuck here on how to sign the token with the Private key from the private.key file
    //---the method used for SignData does not work
    string signature =  SignData(token, data);

    signature = Encode(Encoding.UTF8.GetBytes(signature));

    return signedClientAssertion = string.Concat(token, ".", signature);
    

Private.json 有如下内容


        "kty": "EC",
        "d": "AfJ_hlRFCP0g2PghjghjghjtryrtytyFpbALpoG0gqh9tyaSv8JIZuhKYOgvbAzkI6pi2gdCce3fvWb5csiL24PiS9Ke5CKlh3QyW-YOO",
        "use": "sig",
        "crv": "P-521",
        "kid": "TST_Staging",
        "x": "ADRSCG8Acsqj6SlShpEJYa9UhA7ojghjgjK4eUVHj9CDqbH4j2_F84j7qtK4fdH94xGzYqQwV0rLfJrAISknoudPQm743H",
        "y": "AYnLkWp3Up69WQoc-kZ8ugvSiCNChMiBra3jLHmSotDdzSJ6MgMCokfRdHsfsF-z4VAGq3zam1Z604_rC5N9xrtyrtyufV",
        "alg": "ES512"
    

有人可以指出如何使用私钥签署令牌会很有帮助。谢谢。

【问题讨论】:

【参考方案1】:

我自己设法找到了解决方案。我正在使用 EC Json 公钥和私钥。以前我使用的算法 ES512 在解密来自服务器的 JWE 响应时出现了一些问题,因此我将我的 JWT 密钥算法更改为 ES256,并且这种加密和解密与 Jose.JWT 完美配合。

一个多月以来,我一直在努力寻找 EC Json 公钥和私钥加密和解密的解决方案。所有可用的解决方案都仅适用于 PEM 密钥方法,而 Json EC 密钥则没有太多。我花了很多时间找到必要的部分并将它们组合在一起并使其发挥作用。我正在提供发生加密和解密的页面的完整代码,以便有人可能会发现它有用。

using System.Net;
using System.Text;  // For class Encoding
using System.IO;    // For StreamReader
using System;
using System.Collections.Generic;
using System.Configuration;
using Jose;
using System.Security.Cryptography;
using System.Web;
using Jose.keys;
using Stream = System.IO.Stream;
using System.Web.Script.Serialization;
using Newtonsoft.Json;

public partial class holding : System.Web.UI.Page

    string ClientID = "mlDBKnXXXXXTYRTYRTYyYdmzFGD6HcBPsHZ2W";
    string RemoteURL = "https://www.example.org";
    string RemoteTokenAppend = "/token";
    string RedirectURL = "https://www.exampleabc.com/holding.aspx";
    //-----private key for Encrypting the token
    string PrivateKey = @" 
        
            ""x"": ""4kELRTR545GDGDGtvilOLrtr5luaQaWgaTlpqUf7o"",
            ""y"": ""iCyNdwX73FWKJTjn1Q19gdjEILKjEILK3Y_XwgY3Y_XwgY"",
            ""d"": ""wiYrwNa5SgBNgdqRtSMpaUvRmipaBJ6hfmL1CUMpwlQ"",
            ""kty"": ""EC"",
            ""crv"": ""P-256""
        ";
    Dictionary<string, object> PrivateKeyHeader = new Dictionary<string, object>
        
             "typ", "JWT" ,
             "kid", "TST_SERVER" ,
             "alg", "ES256" 
        ;
    //----Public Enc key for Decrypting the JWE token from the remote host
    string PublicKey_Enc = @" 
        
            ""x"": ""_ylhMfdVwaRrLx8HL8z7X1ixVkk2rbpwD9oU-uAqyhE"",
            ""y"": ""aCRo4kY2dTl7wZXjsp2NJyF9Tcmzk1XZN5ueJWNq7Lk"",
            ""d"": ""Jz9aEpbt_4aKL5FVdCLlux7U-Ubt_4aKL5VdCLLTR2Y"",
            ""kty"": ""EC"",
            ""crv"": ""P-256""
        ";

    public void Page_Load(object sender, EventArgs e)
    
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(RemoteURL + RemoteTokenAppend);
        ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
        request.ProtocolVersion = HttpVersion.Version10;
        var postData = "client_assertion_type=" + HttpUtility.UrlEncode("urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
        postData += "&client_id=" + HttpUtility.UrlEncode(ClientID);
        postData += "&grant_type=" + HttpUtility.UrlEncode("authorization_code");
        postData += "&redirect_uri=" + HttpUtility.UrlEncode(RedirectURL);
        postData += "&code=" + HttpUtility.UrlEncode(Request.QueryString["code"]);
        postData += "&client_assertion=" + EncryptTokenJose();

        var data = Encoding.ASCII.GetBytes(postData);

        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        request.Headers.Add("Content-Encoding", "ISO-8859-1");

        request.ContentLength = data.Length;
        try
        
            using (var stream = request.GetRequestStream())
            
                stream.Write(data, 0, data.Length);
            

            var webResponse = (HttpWebResponse)request.GetResponse();
            var webStream = webResponse.GetResponseStream();
            var responseReader = new StreamReader(webStream);
            var response = responseReader.ReadToEnd();
            //---Decrypt the Response from Remote
            string JoseRes = DecryptTokenJose(response.ToString());
            //---close the response reader
            responseReader.Close();
            Response.Write("<br><br>Payload = " + JoseRes + "<br><br>");
            //---Get the json string and values from the decrypted Token
            JsonTextReader reader = new JsonTextReader(new StringReader(JoseRes));
            var sData = JsonSerializer.CreateDefault().Deserialize<payload>(reader);

            //----Sample Payload
            //---- Payload =  "sub":"s=S775566X,u=c57acrtereb8-d102-455a-860a-ae7dretef4b8d","aud":"mlDBKnGyYfgdgdmzwx770mqXKb6HcBPsHZ2W","amr":["pwd","swk"],"iss":"https:\/\/www.exampleabc.com","exp":1637810980,"iat":1637810380,"nonce":"TJtv85f586sTgxjUlFm5"
            //----get the sub from Payload
            string sub = sData.sub;

            Response.Write("<br><br>Sub = " + sub + "<br><br>");
            //---Get the list array of sub to extract the IC
            string[] subList = sub.Split(',');

            //---Get the first item of the array to get the IC
            Response.Write("<br><br>NRIC = " + subList[0] + "<br><br>");

            //---Retrieved IC value will be s=S775566X, so replace s= to "" to get the exact NRIC value and set the Session value
            Session["NRIC"] = subList[0].Replace("s=", "");

            //---if Session["RedirctPage"] is set then go to that page
            if (Session["RedirctPage"] != null)
                Response.Redirect(Session["RedirctPage"].ToString());
            else
                Response.Redirect("Register_uat.aspx");

        
        catch (WebException ex)
        

            using (WebResponse response = ex.Response)
            


                string ErrorString = "Error from the Server:-----<br><br>";
                HttpWebResponse httpResponse = (HttpWebResponse)response;                   
                using (Stream data1 = response.GetResponseStream())
                using (var reader = new StreamReader(data1))
                
                    ErrorString += reader.ReadToEnd();
                    Response.Write(ErrorString);
                

                
            
        
    

    public string EncryptTokenJose()
    

        const uint JwtToAadLifetimeInSeconds = 60 * 2; 
        DateTime validFrom = DateTime.UtcNow;
        long exp = ConvertToTimeT(validFrom + TimeSpan.FromSeconds(JwtToAadLifetimeInSeconds));
        long iat = ConvertToTimeT(validFrom);
        //---Payload string
        string payloadStr = "\"aud\":\"" + RemoteURL + "\",\"exp\":" + exp.ToString() + ",\"iss\":\"" + ClientID + "\",\"iat\": " + iat.ToString() + ",\"sub\":\"" + ClientID + "\"";
        //---get the Private key to form the public key
        JsonTextReader reader = new JsonTextReader(new StringReader(PrivateKey));
        var jwk = JsonSerializer.CreateDefault().Deserialize<JWK>(reader);
        var publicECCKey = EccKey.New(Base64Url.Decode(jwk.x), Base64Url.Decode(jwk.y), Base64Url.Decode(jwk.d), usage: CngKeyUsages.KeyAgreement);
        string token = Jose.JWT.Encode(payloadStr, publicECCKey, JwsAlgorithm.ES256, extraHeaders: PrivateKeyHeader);
        return token;
    

    public string DecryptTokenJose(string Res)
    
        var jss = new javascriptSerializer();
        string json = Res;
        Dictionary<string, string> sData = jss.Deserialize<Dictionary<string, string>>(json);
        string AccessToken = sData["access_token"].ToString();
        string TokenType = sData["token_type"].ToString();
        string IdToken = sData["id_token"].ToString();

        Response.Write("<br><br>" + IdToken + "<br><br>");
        //---get the Public key Enc to decrypt the JWE token
        JsonTextReader reader = new JsonTextReader(new StringReader(PublicKey_Enc));
        var jwk = JsonSerializer.CreateDefault().Deserialize<JWK>(reader);
        var publicECCKey = EccKey.New(Base64Url.Decode(jwk.x), Base64Url.Decode(jwk.y), Base64Url.Decode(jwk.d), usage: CngKeyUsages.KeyAgreement);

        //---get the decrypted token
        string token = Jose.JWT.Decode(IdToken, publicECCKey, JweAlgorithm.ECDH_ES_A128KW, JweEncryption.A256CBC_HS512);

        //----todo: Verify the signature of the decoded JWS token

        //----5 parts token with dot(.) as separator
        string[] toklist = token.Split('.');
        //----get the 2nd item of the list which will have the Payload with User IC details
        string Payload = toklist[1];
        //----decode the payload to bytes and from bytes to readable string
        var base64EncodedBytes = Base64Url.Decode(Payload);
        return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);

    

    public static long ConvertToTimeT(DateTime dt)
    
        return (long)(dt - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds;
    

    public class payload
    
        public string sub  get; set; 
        public string aud  get; set; 
        public string iss  get; set; 
        public string exp  get; set; 
        public string iat  get; set; 

    

    public class JWK
    
        public string x  get; set; 
        public string y  get; set; 
        public string d  get; set; 
    

【讨论】:

以上是关于在 C# 中使用 json 私钥对 Header 和 Payload 进行签名的主要内容,如果未能解决你的问题,请参考以下文章

如何使用openssl生成RSA公钥和私钥对 / 蓝讯

使用来自 etoken c# 的不可导出私钥签署 xml 请求

我可以从智能卡中的密钥容器中获取公钥/私钥对吗?

无法为新用户生成有效的 ssh 公钥/私钥对

openssl生成RSA公钥和私钥对

SSH 在 Windows 上为公钥/私钥对寻找错误的位置