• Jwt Token 安全策略使用 ECDSA 椭圆曲线加密算法签名/验证


    椭圆曲线密码学(Elliptic curve cryptography),简称 ECC,是一种建立公开密钥加密的算法,也就是非对称加密,ECDH 与 ECDSA 是基于 ECC 的算法。类似的还有 RSA,ElGamal 算法等。ECC 被公认为在给定密钥长度下最安全的加密算法。比特币中的公私钥生成以及签名算法 ECDSA 都是基于 ECC 的。之前介绍 JWT 相关的知识介绍过了 HS256(MAC),RS256 (RSA) 相关的签名与验证,还有一种非对称签名算法 ES256 算法(ECDSA)也是推荐使用的一种。这三种算法也是不同语言主要实现,微软 System.IdentityModel.Tokens.Jwt 支持情况可以在 https://jwt.io/ 中找到。

    https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ 一文中提到了使用 JWT的“none”算法的安全性以及提供了一个密钥字段(kid)验证的重要性。

    JSON Web Algorithms (JWA)[RFC7518] 

       +--------------+-------------------------------+--------------------+
       | "alg" Param  | Digital Signature or MAC      | Implementation     |
       | Value        | Algorithm                     | Requirements       |
       +--------------+-------------------------------+--------------------+
       | HS256        | HMAC using SHA-256            | Required           |
       | HS384        | HMAC using SHA-384            | Optional           |
       | HS512        | HMAC using SHA-512            | Optional           |
       | RS256        | RSASSA-PKCS1-v1_5 using       | Recommended        |
       |              | SHA-256                       |                    |
       | RS384        | RSASSA-PKCS1-v1_5 using       | Optional           |
       |              | SHA-384                       |                    |
       | RS512        | RSASSA-PKCS1-v1_5 using       | Optional           |
       |              | SHA-512                       |                    |
       | ES256        | ECDSA using P-256 and SHA-256 | Recommended+       |
       | ES384        | ECDSA using P-384 and SHA-384 | Optional           |
       | ES512        | ECDSA using P-521 and SHA-512 | Optional           |
       | PS256        | RSASSA-PSS using SHA-256 and  | Optional           |
       |              | MGF1 with SHA-256             |                    |
       | PS384        | RSASSA-PSS using SHA-384 and  | Optional           |
       |              | MGF1 with SHA-384             |                    |
       | PS512        | RSASSA-PSS using SHA-512 and  | Optional           |
       |              | MGF1 with SHA-512             |                    |
       | none         | No digital signature or MAC   | Optional           |
       |              | performed                     |                    |
       +--------------+-------------------------------+--------------------+

    ECDSA(Digital Signature Algorithm,椭圆曲线签名与校验,数字签名算法)它是另一种公开密钥算法,它不能用作加密,只用作数字签名。DSA使用公开密钥,为接受者验证数据的完整性和数据发送者的身份。它也可用于由第三方去确定签名和所签数据的真实性。

    image

    openssl 创建证书

    openssl ecparam -genkey -name secp256r1 -out ecdas.key
    openssl req -new -key ecdas.key -out myreq.csr
    openssl req -x509 -days 7 -key ecdas.key -in myreq.csr -out ecdas.crt
    openssl pkcs12 -export -out ecdas.pfx -inkey ecdas.key -in ecdas.crt
    简化
    openssl ecparam -genkey -name secp256r1 | openssl ec -out ecdas.key
    openssl req -new -x509 -days 365 -key ecdas.key -out ecdas.crt -subj "/C=CN/L=SH/O=PICC/CN=idsvr4.com"
    openssl pkcs12 -export -in ecdas.crt -inkey ecdas.key -out ecdas.pfx

    .net core 实现

    class Program
        {
            static void Main(string[] args)
            {
                //获得证书文件
                var filePath = Path.Combine(AppContext.BaseDirectory, "Certs\ecdas.pfx");
                if (!File.Exists(filePath))
                {
                    throw new FileNotFoundException("Signing Certificate is missing!");
                }
                var x509Cert = new X509Certificate2(filePath, "123456");
                var data = new byte[] { 21, 5, 8, 12, 207 };
    
                //test signature
                var signature = ECDsaSignData(x509Cert, data);
                Console.WriteLine(ECDsaVerifyData(x509Cert, data, signature) ? "Valid!" : "Not Valid...");
    
                //test certs signature jwt(openssl)
                var jwtToken = CreateSignedJwt(x509Cert.GetECDsaPrivateKey());
                Console.WriteLine(VerifySignedJwt(x509Cert.GetECDsaPublicKey(), jwtToken) ? "Valid!" : "Not Valid...");
    
                //test certs signature jwt by BouncyCastle
                string privateKey = "c711e5080f2b58260fe19741a7913e8301c1128ec8e80b8009406e5047e6e1ef";
                string publicKey = "04e33993f0210a4973a94c26667007d1b56fe886e8b3c2afdd66aa9e4937478ad20acfbdc666e3cec3510ce85d40365fc2045e5adb7e675198cf57c6638efa1bdb";
                var privateECDsa = LoadPrivateKey(FromHexString(privateKey));
                var publicECDsa = LoadPublicKey(FromHexString(publicKey));
                var jwt = CreateSignedJwt(privateECDsa);
                var isValid = VerifySignedJwt(publicECDsa, jwt);
                Console.WriteLine(isValid ? "Valid!" : "Not Valid...");
    
                //test certs signature jwt by Create Private-Public Key pair(https://github.com/smuthiya/EcdsaJwtSigning/blob/master/Program.cs)
                var key = CngKey.Create(CngAlgorithm.ECDsaP256, "ECDsaKey", new CngKeyCreationParameters
                {
                    KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
                    KeyUsage = CngKeyUsages.AllUsages,
                    ExportPolicy = CngExportPolicies.AllowPlaintextExport
                });
                var cngKey_privateKey = new ECDsaCng(CngKey.Import(key.Export(CngKeyBlobFormat.EccPrivateBlob), CngKeyBlobFormat.EccPrivateBlob));
                cngKey_privateKey.HashAlgorithm = CngAlgorithm.ECDsaP256;
                var cngKey_publicKey = new ECDsaCng(CngKey.Import(key.Export(CngKeyBlobFormat.EccPublicBlob), CngKeyBlobFormat.EccPublicBlob));
                cngKey_publicKey.HashAlgorithm = CngAlgorithm.ECDsaP256;
                var jwt_sign = CreateSignedJwt(privateECDsa);
                Console.WriteLine(VerifySignedJwt(publicECDsa, jwt_sign) ? "Valid!" : "Not Valid...");
                Console.ReadKey();
            }
    
            private static byte[] ECDsaSignData(X509Certificate2 cert, byte[] data)
            {
                using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
                {
                    if (ecdsa == null)
                        throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
                    return ecdsa.SignData(data, HashAlgorithmName.SHA256);
                }
            }
    
            private static bool ECDsaVerifyData(X509Certificate2 cert, byte[] data, byte[] signature)
            {
                using (ECDsa ecdsa = cert.GetECDsaPublicKey())
                {
                    if (ecdsa == null)
                        throw new ArgumentException("Cert must be an ECDSA cert", nameof(cert));
                    return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
                }
            }
    
            private static byte[] FromHexString(string hex)
            {
                var numberChars = hex.Length;
                var hexAsBytes = new byte[numberChars / 2];
                for (var i = 0; i < numberChars; i += 2)
                    hexAsBytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
                return hexAsBytes;
            }
    
            private static bool VerifySignedJwt(ECDsa eCDsa, string token)
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidIssuer = "me",
                    ValidAudience = "you",
                    IssuerSigningKey = new ECDsaSecurityKey(eCDsa)
                }, out var parsedToken);
                return claimsPrincipal.Identity.IsAuthenticated;
            }
    
            private static string CreateSignedJwt(ECDsa eCDsa)
            {
                var now = DateTime.UtcNow;
                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtToken = tokenHandler.CreateJwtSecurityToken(
                    issuer: "me",
                    audience: "you",
                    subject: null,
                    notBefore: now,
                    expires: now.AddMinutes(30),
                    issuedAt: now,
                    signingCredentials: new SigningCredentials(new ECDsaSecurityKey(eCDsa), SecurityAlgorithms.EcdsaSha256));
                return tokenHandler.WriteToken(jwtToken);
            }
    
            private static ECDsa LoadPrivateKey(byte[] key)
            {
                var privKeyInt = new Org.BouncyCastle.Math.BigInteger(+1, key);
                var parameters = SecNamedCurves.GetByName("secp256r1");
                var ecPoint = parameters.G.Multiply(privKeyInt);
                var privKeyX = ecPoint.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned();
                var privKeyY = ecPoint.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned();
    
                return ECDsa.Create(new ECParameters
                {
                    Curve = ECCurve.NamedCurves.nistP256,
                    D = privKeyInt.ToByteArrayUnsigned(),
                    Q = new ECPoint
                    {
                        X = privKeyX,
                        Y = privKeyY
                    }
                });
            }
            private static ECDsa LoadPublicKey(byte[] key)
            {
                var pubKeyX = key.Skip(1).Take(32).ToArray();
                var pubKeyY = key.Skip(33).ToArray();
                return ECDsa.Create(new ECParameters
                {
                    Curve = ECCurve.NamedCurves.nistP256,
                    Q = new ECPoint
                    {
                        X = pubKeyX,
                        Y = pubKeyY
                    }
                });
            }
        }

    备注:IdentityServer4 目前暂时仅支持 RS256 ( RSA with SHA256) ,还不支持 ES256 (https://github.com/IdentityServer/IdentityServer4/issues/2493)。

    JAVA 的话可以选用 Google 的 Tink

        @Test
        public void testECDSA_P256() {
    
            try {
                TinkConfig.register();
                String plaintext = "napier";
                KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
                PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);
                byte[] signature = signer.sign(plaintext.getBytes());
                byte[] encoded = Base64.getEncoder().encode(signature);
                System.out.println("
    MAC (Base64):	" + new String(encoded));
                System.out.println("MAC (Hex):" + toHexString(encoded));
                KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();
                PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(publicKeysetHandle);
                try {
                    verifier.verify(signature, plaintext.getBytes());
                    System.out.println("
    Valid Signature");
                } catch (GeneralSecurityException e) {
                    System.out.println("In Valid Signature");
                }
                System.out.println("
    Printing out key:");
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                CleartextKeysetHandle.write(privateKeysetHandle, JsonKeysetWriter.withOutputStream(outputStream));
                System.out.println(new String(outputStream.toByteArray()));
            } catch (Exception e) {
                System.out.println(e);
                System.exit(1);
            }
        }
    
        public static String toHexString(byte[] bytes) {
            StringBuffer sb = new StringBuffer(bytes.length * 2);
            for (int i = 0; i < bytes.length; i++) {
                sb.append(toHex(bytes[i] >> 4));
                sb.append(toHex(bytes[i]));
            }
            return sb.toString();
        }
    
        private static char toHex(int nibble) {
            final char[] hexDigit =
                    {
                            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
                    };
            return hexDigit[nibble & 0xF];
        }

    备注:

    今天找到了一个好的 OIDC 的 JAVA 客户端,也包含了JWT 不同算法签名 ,更多参考:https://connect2id.com/products/nimbus-jose-jwt/examples

    REFER:
    https://docs.microsoft.com/en-gb/dotnet/api/system.security.cryptography?view=netcore-2.0
    https://www.ibm.com/support/knowledgecenter/zh/SSMNED_5.0.0/com.ibm.apic.toolkit.doc/rapim_ref_ootb_policyjwtgen.html
    https://www.scottbrady91.com/C-Sharp/JWT-Signing-using-ECDSA-in-dotnet-Core
    https://www.openssl.org/docs/manmaster/man1/ecparam.html
    https://www.bouncycastle.org/csharp/
    https://yq.aliyun.com/articles/551057
    http://www.cnblogs.com/linianhui/p/security-based-toolbox.html
    http://bobao.360.cn/news/detail/1377.html
    https://www.cnblogs.com/Kalafinaian/p/7392505.html
    https://zhuanlan.zhihu.com/p/27615345
    https://zhuanlan.zhihu.com/p/36326221
    http://8btc.com/thread-1240-1-1.html
    https://juejin.im/post/5a67f3836fb9a01c9b661bd3
    https://zhuanlan.zhihu.com/p/36326221
    https://www.bouncycastle.org/
    https://medium.com/coinmonks/cryptography-with-google-tink-33a70d71918d

  • 相关阅读:
    试着把.net的GC讲清楚(2)
    试着把.net的GC讲清楚(1)
    【特性】select语句中使用字符串链接获取字段值失败
    twemproxy分片处理原理--剖析twemproxy代码正编
    robot framework的使用说明
    网络故障模拟,cpu高压以及docker中的实现
    我眼中的robot framework
    twemproxyMemcache协议解析探索——剖析twemproxy代码正编补充
    twemproxy代理主干流程——剖析twemproxy代码正编
    Leetcode 617 Merge Two Binary Trees 二叉树
  • 原文地址:https://www.cnblogs.com/Irving/p/9390588.html
Copyright © 2020-2023  润新知