For token based authentication Microsoft.IdentityModel.Tokens provides a list of security algorithms that can be used to create SigningCredentials:
string secretKey = "MySuperSecretKey";
byte[] keybytes = Encoding.ASCII.GetBytes(secretKey);
SecurityKey securityKey = new SymmetricSecurityKey(keybytes);
SigningCredentials signingCredentials =
new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256);
SigningCredentials signingCredentials =
new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256Signature);
What is the difference between HmacSha256 and HmacSha256Signature? When would you use the signature one instead of the non-signature one?**
There are other "non signature" and "signature" algorithms as well. For example, RsaSha256 and RsaSha256Signature
HmacSha256 is a string constant evaluating to "HS256". HmacSha256Signature is also a string constant but evaluates to "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"
The latest definition of System.IdentityModel.Tokens.SecurityAlgorithms does not include HmacSha256 but instead allows you to separate the signature and digest algorithms for the SigningCredentials.
You should use HmacSha256Signature for future-proofing your application as HmacSha256 looks deprecated.
From the Microsoft docs...
The members that have a Signature suffix can be used to specify the
signatureAlgoritm parameter and the members that have a Digest suffix
can be used to specify the digestAlgorithm parameter.
Related
My sample project
Summary
I have created a sample project to test the issuing of JWT tokens in an ASP.Net Core applications.
Refer to the Github repo above for the full sample.
In the JwtService.cs class, there is a method called GenerateSecurityToken, which generates the Jwt token.
The Login method of the AccountController calls the JwtService class to generate the token, saves the token in a cookie and also sets another cookie for the user id.
Note: The token is signed with a secret, which is also appended with a user specific salt. The user salt can be invalidated at any point, making the user's token invalid.
The token is also encrypted with an encryption key. This makes the body of the token illegible when inspected in JWT.io. I believe it is called a JWE.
This question discusses the signing and encrypting order.
Issue
I do not want the bearer or any third party to inspect the content of the token. The token's purpose in authentication.
My sample code intercepts the authentication pipeline, and uses the email from the cookie to pull user roles out of the database and creates role claims out of the roles.
A few of you may have figured out the information leak caused by issuing a separate cookie with the email.
Ideally, I want to ONLY issue the JWE token in the cookie.
I want to intercept the authentication pipeline, decrypt the token, use the email claim to get the user salt (from the db) and validate the token.
I have read the documentation, but cannot figure out a way to decrypt the token.
To be honest, I am not even sure of the order of operation (sign then encrypt or encrypt then sign), when the token is issued.
If anyone could even point me to the source code for JwtSecurityTokenHandler that might be a good start.
TIA
public string GenerateSecurityToken(string email, byte[] salt, string[] roles)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_secret).Concat(salt).ToArray();
byte[] ecKey = new byte[256 / 8];
Array.Copy(Encoding.ASCII.GetBytes(_ecKey), ecKey, 256 / 8);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _issuer,
Audience = _audience,
Subject = new ClaimsIdentity(
new List<Claim>
{
new Claim(ClaimTypes.Email, email)
}),
// .Concat(roles.Select(r => new Claim(ClaimTypes.Role, r))).ToArray()),
Expires = DateTime.UtcNow.AddMinutes(double.Parse(_expDate)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
EncryptingCredentials = new EncryptingCredentials(
new SymmetricSecurityKey(
ecKey),
SecurityAlgorithms.Aes256KW,
SecurityAlgorithms.Aes256CbcHmacSha512)
};
var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
While you are very interested in hashing the JWT, I recommend you have four methods:
To generates the token (Sequence is important):
GenerateToken
HashToken
Then when you receive the bearer token with the request (Sequence is important):
DeHashToken
ValidateToken
As both processes, JWT generating and hashing are very straightforward:
To get the best results, forget about the hashing and focus on Generate Token and Validate Token.
After you get success with JWT, in a separate project, focus on Hashing and DeHashing any text (JWT or something else).
When you succeed with both, don't call each other from each other.
Build new Methods like
GenerateAndHash (It calls both GenerateToken and HashToken)
VerfyHashAndValidateToken (It calls both DeHashToken and ValidateToken)
To Generate and Validate JWT step by step see this (The title of the article could be confusing but it talks about JWT in .NET Core in a very simple way especially in part 2 I think)
https://www.codemag.com/Article/1805021/Security-in-Angular-Part-1
https://www.codemag.com/Article/1809031/Security-in-Angular-Part-2
https://www.codemag.com/Article/1811031/Security-in-Angular-Part-3
To Hash
public static string ToCustomHash(this string text)
{
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
var pbkdf2 = new Rfc2898DeriveBytes(text, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
var hashedToBase64 = Convert.ToBase64String(hashBytes);
return hashedToBase64;
}
To Verify The hash
public static bool VerifyHashWith(this string storedPassword, string loginPassword)
{
byte[] hashBytes = Convert.FromBase64String(storedPassword);
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
var pbkdf2 = new Rfc2898DeriveBytes(loginPassword, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
for (int i = 0; i < 20; i++)
{
if (hashBytes[i + 16] != hash[i]) { return false; }
}
return true;
}
How we can create AsymmetricSecurityKey in c#. Actually we are creating signing credentials with AsymetricSecurityKey here is our code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new AsymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(securityKey, SecurityAlgorithms.HmacSha256Signature);
You can generate public/private keys using:
public void GenerateRsaCryptoServiceProviderKey()
{
var rsaProvider = new RSACryptoServiceProvider(512);
SecurityKey key = new RsaSecurityKey(rsaProvider);
}
You should use RsaSha256 below:
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(key, SecurityAlgorithms.RsaSha256);
Signing credentials with a AsymmetricSecurityKey in C# using a RSA private key:
// RSA Private Key Base64
var privateKey = #"...";
var privateKeyBuffer = new Span<byte>(new byte[privateKey.Length]);
Convert.TryFromBase64String(privateKey, privateKeyBuffer, out _);
// abstract class RSA : AsymmetricAlgorithm in namespace System.Security.Cryptography
var rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportRSAPrivateKey(privateKeyBuffer, out _);
// class RsaSecurityKey : AsymmetricSecurityKey in namespace Microsoft.IdentityModel.Tokens
var rsaSecurityKey = new RsaSecurityKey(rsaPrivateKey);
var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256);
This is a possible solution on how to create AsymmetricSecurityKey object and a SigningCredentials object when we have a RSA private key (asymmetric key) in string format.
When you want to use asymmetric keys that are generated outside your application, you may need this additional steps to import an externally generated key.
Are you specifically looking for an AsymmetricSecurityKey?
I noticed that you are referencing the HM256 algorithm. That leads me to believe that you are looking for a SymmetricSecurityKey. Also, your approach seems very specific to using the HMAC alg.
To generate a SymmetricSecurityKey, you can try something like the following code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
If you would like the a solution for using an RS256 alg (which will use a cert in pfx format), you can comment and I will do my best to give you an example of that too.
This creates security key from an RSA public key in F#.
let pem = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB"
let getPublicKey (pem: string) =
let publicKey = ReadOnlySpan<byte>(Convert.FromBase64String(pem))
let rsa = RSA.Create()
let mutable read = 0
rsa.ImportSubjectPublicKeyInfo(publicKey, &read)
new RsaSecurityKey(rsa)
getPublicKey pem
How can I (can I?) use X509SecurityKey for Asp.Net Core JWT validation?
My current code is roughly:
X509SecurityKey signingKey = null;
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var v = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
var v1 = v.Find(X509FindType.FindBySubjectDistinguishedName, strCertName, true);
signingKey = new X509SecurityKey(v1[0]);
}
and later on for the signing credentials...
new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
This causes an exception:
SignatureAlgorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.X509SecurityKey'
is not supported.
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
I tried a few algorithms, but it doesn't seem like it works with any of them?
I tried a few algorithms, but it doesn't seem like it works with any of them?
You're trying to use an asymmetric key (embedded in a X.509 certificate) with a HMAC algorithm (that we often abusively call "symmetric signature algorithm"): this cannot work.
Assuming your certificate is a RSA certificate, you should be able to use SecurityAlgorithms.RsaSha256.
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256)
I am building login workflow using Google for user. Once user is authenticated, I call GetAuthResponse to get idToken.
https://developers.google.com/identity/sign-in/web/backend-auth
Now, I need to verify certificate against Google certificate. I am using JwtSecurityToken(C#) for the same.
I am referencing for verification - http://blogs.msdn.com/b/alejacma/archive/2008/06/25/how-to-sign-and-verify-the-signature-with-net-and-a-certificate-c.aspx
Issue is - I always gets false from VerifyHash. As, VerifyHash returns just false without any reason, I am not able to find way to verify whether idToken is
valid or not. My code is given below
String strID = ""; // idToken received from Google AuthResponse
JwtSecurityToken token = new JwtSecurityToken(strID);
byte[] text = GetHash(token.RawData);
SHA256Cng sha1 = new SHA256Cng();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
byte[] signature = Encoding.Unicode.GetBytes(token.RawSignature);
// Modulus and exponent value from https://www.googleapis.com/oauth2/v2/certs - second set of keys
String modulus = "uHzGq7cMlx21nydbz9VsW1PItetb9mqvnpLp_8E3Knyk-mjv9DlaPhKGHYlJfHYGzKa2190C5vfsLLb1MIeGfdAv7ftpFsanIWawl8Zo0g-l0m7T2yG_7XerqcVK91lFifeJtgxKI86cPdZkgRy6DaYxMuAwAlhvpi3_UhPvsIwi7M6mxE8nUNpUWodh_YjJNu3wOxKDwbBZuRV2itjY6Z7RjFgJt1CsKF-QjqSVvWjAl0LaCaeMS_8yae0ln5YNeS8rAb6xkmcOuYeyhYsiBzwLRvgpXzEVLjLr631Z99oUHTpP9vWJDpGhfkrClkbmdtZ-ZCwX-eFW6ndd54BJEQ==";
String exponent = "AQAB";
modulus = modulus.Replace('-', '+').Replace('_', '/'); // Else it gives Base64 error
StringBuilder sb = new StringBuilder();
sb.Append("<RSAKeyValue>");
sb.Append("<Modulus>");
sb.Append(modulus);
sb.Append("</Modulus>");
sb.Append("<Exponent>");
sb.Append(exponent);
sb.Append("</Exponent>");
sb.Append("</RSAKeyValue>");
RSACryptoServiceProvider RSAVerifier = new RSACryptoServiceProvider();
RSAVerifier.FromXmlString(sb.ToString());
// Verify the signature with the hash
return RSAVerifier.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature);
You might want to try as done in the Google+ Token Verification project - this fork includes a few minor updates that are still in review.
An alternative approach is to just verify the tokens using Google's token verification endpoints:
curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjkyNGE0NjA2NDgxM2I5YTA5ZmFjZGJiNzYwZGI5OTMwMWU0ZjBkZjAifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwNTcwOTc3MjI2ODMwNTc3MjMwIiwiYXpwIjoiMzY0MzgxNDQxMzEwLXRuOGw2ZnY2OWdnOGY3a3VjanJhYTFyZWpmaXRxbGpuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlAzLU1HZTdocWZhUkZ5Si1qcWRidHciLCJhdWQiOiIzNjQzODE0NDEzMTAtdG44bDZmdjY5Z2c4ZjdrdWNqcmFhMXJlamZpdHFsam4uYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJjX2hhc2giOiJjd3hsdXBUSkc4N2FnbU1pb0tSYUV3IiwiaWF0IjoxNDM0NDcyODc2LCJleHAiOjE0MzQ0NzY0NzZ9.Gz_WljZOV9NphDdClakLstutEKk65PNpEof7mxM2j-AOfVwh-SS0L5uxIaknFOk4-nDGmip42vrPYgNvbQWKZY63XuCs94YQgVVmTNCTJnao1IavtrhYvpDqGuGKdEB3Wemg5sS81pEthdvHwyxfwLPYukIhT8-u4ESfbFacsRtR77QRIOk-iLJAVYWTROJ05Gpa-EkTunEBVmZyYetbMfSoYkbwFKxYOlHLY-ENz_XfHTGhYhb-GyGrrw0r4FyHb81IWJ6Jf-7w6y3RiUJik7kYRkvnFouXUFSm8GBwxsioi9AAkavUWUk27s15Kcv-_hkPXzVrW5SvR1zoTI_IMw
This code fails every time I pass it an APK file that has a certification chain (intermediate and Root CA). If the file is self-signed it works correctly.
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)Cert.PublicKey.Key;
bool verified = csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), vData)
"hash" is the sha1 digest of the signature file (SF) and vData the encrypted hash of a signature (CMSG_ENCRYPTED_DIGEST,), both are byte arrays.
I will answer my own question after finding the answer. The problem resides when specifying the digest method.
A X509 certificate uses two algorithms that may not coincide (SignatureAlgorithm.FriendlyName and PublicKey.Key.SignatureAlgorithm). When verifying the hash you must make sure that it matches the SignatureAlgorithm of the certificate and not the algorithm used in the public key (which was my fault).
If in doubt, check both:
if (cert.SignatureAlgorithm.FriendlyName.ToString().Contains("sha256"))
{
SHA256Managed sha256 = new SHA256Managed();
byte[] hash256 = sha256.ComputeHash(sig);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
verify_all.Add(csp.VerifyHash(hash256, CryptoConfig.MapNameToOID("SHA256"), vData));
}
else if (cert.SignatureAlgorithm.FriendlyName.ToString().Contains("sha1"))
{
SHA1Managed Sha1 = new SHA1Managed();
byte[] hash1 = sha1.ComputeHash(sig);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
verify_all.Add(csp.VerifyHash(hash1, CryptoConfig.MapNameToOID("SHA256"), vData));
}