Encrypting JWT payload - encryption

JWTs have 3 parts:
HEADER:ALGORITHM & TOKEN TYPE
PAYLOAD:DATA
SIGNATURE TO BE VERIFIED WITH THE SECRET KEY
Is it possible to encrypt the payload? Following is my token's payload:
{
"iss": "joe",
"exp": "1300819380",
"data": {
"id": "12",
"userName": "PH",
"qntRed": "7",
"qntGrad": {
"1": "800",
"2": "858",
"3": "950",
"4": "745",
"5": "981"
}
}
If "qntGrad" contains sensitive data. Can I encrypt it using the secret key? Will it still be a valid JWT?

In fact there is not only signed JWT, but several technologies described by RFCs:
JWS JSON Web Signature (RFC 7515),
JWT JSON Web Token (RFC 7519),
JWE JSON Web Encryption (RFC 7516),
JWA JSON Web Algorithms (RFC 7518).
JWK JSON Web Key (RFC 7517).
In your case, read the RFC7516 (JWE). These JWE have 5 parts:
Protected Header
Encrypted Key
Initialization Vector
Ciphertext
Authentication Tag
Depending on your platform, you may find a library that will help you to create such encrypted JWT. Concerning PHP, I am writing a library that is already able to load and create these jose.

Below is a very simple and effective method for encrypting using AES. Note you will need to get your own key (link included in comments).
Note that when you encrypt, it will set an IV for each encryption call. You will need this to decrypt.
public class CustomEncryption
{
public static string Encrypt256(string text, byte[] AesKey256, out byte[] iv)
{
// AesCryptoServiceProvider
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 256;
aes.Key = aesKey256();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
iv = aes.IV;
byte[] src = Encoding.Unicode.GetBytes(text);
using (ICryptoTransform encrypt = aes.CreateEncryptor())
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
return Convert.ToBase64String(dest);
}
}
public static string Decrypt256(string text, byte[] AesKey256, byte[] iv)
{
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 256;
aes.IV = iv;
aes.Key = aesKey256();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] src = System.Convert.FromBase64String(text);
using (ICryptoTransform decrypt = aes.CreateDecryptor())
{
byte[] dest = decrypt.TransformFinalBlock(src, 0, src.Length);
return Encoding.Unicode.GetString(dest);
}
}
private static byte[] aesKey256()
{
//you will need to get your own aesKey
//for testing you can generate one from
//https://asecuritysite.com/encryption/keygen
return new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5 };
}
}
}

Not encrypting the token means that other external services can read and validate that the token is in fact authentic without having access to your private key. (They would only need the public key)

Related

encrypt in .net core with TripleDES

public static string Encrypt(string toEncrypt, string secretKey)
{
byte[] keyArray;
byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);
var md5Serv = System.Security.Cryptography.MD5.Create();
keyArray = md5Serv.ComputeHash(UTF8Encoding.UTF8.GetBytes(secretKey));
md5Serv.Dispose();
var tdes = System.Security.Cryptography.TripleDES.Create();
//set the secret key for the tripleDES algorithm
tdes.Key = keyArray;
//mode of operation. there are other 4 modes.
//We choose ECB(Electronic code Book)
tdes.Mode = CipherMode.ECB;
//padding mode(if any extra byte added)
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateEncryptor();
//transform the specified region of bytes array to resultArray
byte[] resultArray =
cTransform.TransformFinalBlock(toEncryptArray, 0,
toEncryptArray.Length);
//Release resources held by TripleDes Encryptor
tdes.Dispose();
//Return the encrypted data into unreadable string format
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
secretkey = 16 character of string
in this line :
tdes.Key = keyArray;
i get this error:
Message = "Specified key is not a valid size for this algorithm."
error Message screen shot
how to solved this problem in asp.net core 1.1.0?
how to convert byte[16] to byte[24]?
Updated Post
thanks For Help :) but!
I use this code in .Net Framework 4.6.2 for encrypt:
public static string Encrypt(string toEncrypt, string secretKey)
{
byte[] keyArray;
byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);
System.Configuration.AppSettingsReader settingsReader = new AppSettingsReader();
MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(secretKey));
hashmd5.Clear();
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
//set the secret key for the tripleDES algorithm
tdes.Key = keyArray;
//mode of operation. there are other 4 modes.
//We choose ECB(Electronic code Book)
tdes.Mode = CipherMode.ECB;
//padding mode(if any extra byte added)
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateEncryptor();
//transform the specified region of bytes array to resultArray
byte[] resultArray =
cTransform.TransformFinalBlock(toEncryptArray, 0,
toEncryptArray.Length);
//Release resources held by TripleDes Encryptor
tdes.Clear();
//Return the encrypted data into unreadable string format
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
and Use this in .Net Core 1.1 :
public static string Encrypt(string toEncrypt, string secretKey)
{
byte[] keyArray;
byte[] resultArray;
byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);
using (var md5Serv = System.Security.Cryptography.MD5.Create())
{
keyArray = md5Serv.ComputeHash(UTF8Encoding.Unicode.GetBytes(secretKey));
if(keyArray.Length==16)
{
byte[] tmp = new byte[24];
Buffer.BlockCopy(keyArray, 0, tmp, 0, keyArray.Length);
Buffer.BlockCopy(keyArray, 0, tmp, keyArray.Length, 8);
keyArray = tmp;
}
}
using (var tdes = System.Security.Cryptography.TripleDES.Create())
{
//set the secret key for the tripleDES algorithm
tdes.Key = keyArray;
//mode of operation. there are other 4 modes.
//We choose ECB(Electronic code Book)
tdes.Mode = CipherMode.ECB;
//padding mode(if any extra byte added)
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tdes.CreateEncryptor();
//transform the specified region of bytes array to resultArray
resultArray =
cTransform.TransformFinalBlock(toEncryptArray, 0,
toEncryptArray.Length);
}
//Return the encrypted data into unreadable string format
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
but i don't know why this methods give me different result?!
if (key.Length == 16)
{
byte[] tmp = new byte[24];
Buffer.BlockCopy(key, 0, tmp, 0, key.Length);
Buffer.BlockCopy(key, 0, tmp, key.Length, 8);
key = tmp;
}
That will turn your 2DES key (k1, k2) into the 3DES key (k1, k2, k1). FWIW, this has been fixed for .NET Core 2.0 (https://github.com/dotnet/corefx/issues/9966).
So, now your code will work again. Though, as others have pointed out in comments, there's a lot going on in your code which is not considered cryptologically sound by modern standards. You should strongly consider taking this as an opportunity to enhance your encryption. (If you can't "because then it can't work with already existing data" then you should take this opportunity to add crypto-agility to your data, to permit you to move to different key schemes and/or algorithms over time.)

Migrating from Membership to Identity when passwordFormat = Encrypted and decryption = AES

Recently I began developing a new MVC app and needed to take an older existing asp.net membership database and convert it into the new(er) Identity system.
If you've found yourself in a similar situation, then likely you've come upon this helpful post from microsoft which gives you great guidance and scripts on converting the database over to the new schema, including the passwords.
To handle the differences in hashing/encryption of passwords between the two systems, they include a custom password hasher, SqlPasswordHasher, which parses the password field (which has been combined into Password|PasswordFormat|Salt) and attempts to duplicate the logic found inside SqlMembershipProvider to compare the incoming password with the stored version.
However, as I (and another commenter on that post) noticed, this handy hasher they provided doesn't handle Encrypted passwords (despite the confusing lingo they use in the post that seems to indicate that it does). It seems like it should, considering they do bring across the password format into the database, but then curiously the code doesn't use it, instead having
int passwordformat = 1;
which is for hashed passwords. What I needed was one that would handle my scenario which is encrypted passwords using the System.Web/MachineKey configuration element's decryptionKey.
If you also are in such a predicament, and are using the AES algorithm (as defined in the decryption property of the machineKey) then my answer below should come to your rescue.
First, let's talk real quick about what the SqlMembershipProvider is doing under the hood. The provider combines the salt, converted to a byte[] with the password, encoded as a unicode byte array, into a single larger byte array, by concatenating the two together. Pretty straightforward. Then it passes this off, through an abstraction (MembershipAdapter) to the MachineKeySection where the real work is done.
The important part about that handoff is that it instructs the MachineKeySection to use an empty IV (intialization vector) and also to perform no signing. That empty IV is the real lynchpin, because the machineKey element has no IV property, so if you've scratched your head and wondered how the providers were handling this aspect, that's how. Once you know that (from digging around the source code) then you can distill down the encryption code in the MachineKeySection code and combine it with the membership provider's code to arrive at a more complete hasher. Full source:
public class SQLPasswordHasher : PasswordHasher
{
public override string HashPassword(string password)
{
return base.HashPassword(password);
}
public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
string[] passwordProperties = hashedPassword.Split('|');
if (passwordProperties.Length != 3)
{
return base.VerifyHashedPassword(hashedPassword, providedPassword);
}
else
{
string passwordHash = passwordProperties[0];
int passwordformat = int.Parse(passwordProperties[1]);
string salt = passwordProperties[2];
if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
//This is copied from the existing SQL providers and is provided only for back-compat.
private string EncryptPassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm hm = HashAlgorithm.Create("SHA1");
if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length;)
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
}
else //MembershipPasswordFormat.Encrypted, aka 2
{
byte[] bEncrypt = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bEncrypt, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bEncrypt, bSalt.Length, bIn.Length);
// Distilled from MachineKeyConfigSection EncryptOrDecryptData function, assuming AES algo and paswordCompatMode=Framework20 (the default)
using (var stream = new MemoryStream())
{
var aes = new AesCryptoServiceProvider();
aes.Key = HexStringToByteArray(MachineKey.DecryptionKey);
aes.GenerateIV();
aes.IV = new byte[aes.IV.Length];
using (var transform = aes.CreateEncryptor())
{
using (var stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write))
{
stream2.Write(bEncrypt, 0, bEncrypt.Length);
stream2.FlushFinalBlock();
bRet = stream.ToArray();
}
}
}
}
return Convert.ToBase64String(bRet);
}
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static MachineKeySection MachineKey
{
get
{
//Get encryption and decryption key information from the configuration.
System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
return cfg.GetSection("system.web/machineKey") as MachineKeySection;
}
}
}
If you have a different algorithm, then the steps will be very close to the same, but you may want to first dive into the source for MachineKeySection and carefully walkthrough how they're initializing things. Happy Coding!

How to check ASP.NET password hash in node.js

First of all I read this Hashing a password using SHA256 and .NET/Node.js and it didn't help me.
I have to verify passwords hashes created in ASP.NET in node.js environment. I was told that passwords are generated using this algorithm: What is default hash algorithm that ASP.NET membership uses?.
I have example password hash and salt (first line is password and second line is salt):
"Password": "jj/rf7OxXM263rPgvLan4M6Is7o=",
"PasswordSalt": "/Eju9rmaJp03e3+z1v5s+A==",
I know that hash algorithm is SHA1 and I know that above hash is generated for input test123. However I can't reproduce hashing algorithm to get same hash for this input. What I tried:
Password = "jj/rf7OxXM263rPgvLan4M6Is7o="
PasswordSalt = "/Eju9rmaJp03e3+z1v5s+A=="
crypto = require("crypto")
sha1 = crypto.createHash("sha1")
PasswordSalt = new Buffer(PasswordSalt, 'base64').toString('utf8')
sha1.update(PasswordSalt+"test123", "utf8")
result = sha1.digest("base64")
console.log(Password)
console.log(result)
Result is:
jj/rf7OxXM263rPgvLan4M6Is7o=
xIjxRod4+HVYzlHZ9xomGGGY6d8=
I was able to get working C# algorithm:
using System.IO;
using System;
using System.Text;
using System.Security.Cryptography;
class Program
{
static string EncodePassword(string pass, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(pass);
byte[] src = Convert.FromBase64String(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inArray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inArray);
}
static void Main()
{
string pass = "test123";
string salt = "/Eju9rmaJp03e3+z1v5s+A==";
string hash = Program.EncodePassword(pass,salt);
Console.WriteLine(hash);
// outputs jj/rf7OxXM263rPgvLan4M6Is7o=
}
}
So now it is just a matter of porting this algorithm to node.js. The problem is that c# somehow magically operates on bytes and I don't know how to do it in node. Consider following code (it does not use any salt - it just creates base64 sha1 from password:
crypto = require("crypto")
pass = 'test123'
sha1 = crypto.createHash("sha1")
buf = new Buffer( pass, 'utf8')
sha1.update(buf)
result = sha1.digest("base64")
console.log(result)
// outputs cojt0Pw//L6ToM8G41aOKFIWh7w=
And in c#
using System.Text;
using System.Security.Cryptography;
string pass = "test123";
byte[] bytes = Encoding.Unicode.GetBytes(pass);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inArray = algorithm.ComputeHash(bytes);
string hash = Convert.ToBase64String(inArray);
Console.WriteLine(hash);
// outputs Oc/baVMs/zM28IqDqsQlJPQc1uk=
I need code in node.js that will return same value as code in c#. Any ideas?
I finally found the right answer here: https://gist.github.com/PalmerEk/1191651 (with little change from 'ucs2' to 'utf16le'):
function dotnet_membership_password_hash(pass, salt)
{
var bytes = new Buffer(pass || '', 'utf16le');
var src = new Buffer(salt || '', 'base64');
var dst = new Buffer(src.length + bytes.length);
src.copy(dst, 0, 0, src.length);
bytes.copy(dst, src.length, 0, bytes.length);
return crypto.createHash('sha1').update(dst).digest('base64');
}
there is a nodejs module which does all the magic for you. No function on stackoverflow worked in my case, but this module works:
https://www.npmjs.com/package/aspnet-identity-pw
var passwordHasher = require('aspnet-identity-pw');
var hashedPassword = passwordHasher.hashPassword('SomePassword');
var isValid = passwordHasher.validatePassword('SomePassword', hashedPassword);
Changing the encoding of the buffer to utf16le works for both examples you provided here.
This is confirmed by the following StackOverflow Answer.
This is further documented at the relevant .Net Framework documentation

Crypto-Js with c#

I am learning about encryption and using crypto-js I have made a Js & c# version.
What I am trying to accomplish is that the JS or c# version will be able to decode each other messages.
For testing I have kept the IV and KEY , paddding and mode the same in both the JS and C# instance.
I have them both decrypting and encrypting data respectivly but what I have yet to accomplish is providing an encrypted from JS be able to decode using c#.
JS
var key = CryptoJS.enc.Base64.parse('7061737323313233');
var iv = CryptoJS.enc.Base64.parse('7061737323313233');
var encrypted = CryptoJS.AES.encrypt("It works", key,
{ keySize: 128 / 8, iv: iv, mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7 });
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
keySize: 128 / 8, iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
document.write('Encrypted :' + encrypted + '<br>');
document.write('Key :' + encrypted.key + '<br>');
document.write('Salt :' + encrypted.salt + '<br>');
document.write('iv :' + encrypted.iv + '<br>');
document.write('Decrypted : ' + decrypted + '<br>');
document.write('utf8 = ' + decrypted.toString(CryptoJS.enc.Utf8) + '<br>');
C#
public void startEncryption(string original )
{
using (RijndaelManaged myRijndael = new RijndaelManaged())
{
//Settings
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
myRijndael.FeedbackSize = 128;
keybytes = Encoding.UTF8.GetBytes("7061737323313233");
//Should be made unique for each message!. TODO
iv = Encoding.UTF8.GetBytes("7061737323313233");
// Encrypt the string to an array of bytes.
encrypted = EncryptStringToBytes(original, keybytes, iv);
//Show Encrypted data
txt_Output.Text = Convert.ToBase64String(encrypted);
// Decrypt the bytes to a string.
string roundtrip = DecryptStringFromBytes(encrypted, keybytes, iv);
//Display the original data and the decrypted data.
Console.WriteLine("Original: {0}", original);
Console.WriteLine("Round Trip: {0}", roundtrip);
}
}
Where the problem arises in decryption.
private void btn_Decrypt_Click(object sender, EventArgs e)
{
Console.WriteLine("Decrypting..");
using (RijndaelManaged myRijndael = new RijndaelManaged())
{
//Settings
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
myRijndael.FeedbackSize = 128;
keybytes = Encoding.UTF8.GetBytes("7061737323313233");
//Should be made unique for each message!. TODO
iv = Encoding.UTF8.GetBytes("7061737323313233");
// Decrypt the bytes to a string.
string roundtrip = DecryptToString(txt_Output.Text);
txt_Output.Text = roundtrip;
//Display the original data and the decrypted data.
}
}
public string DecryptToString(string TextValue)
{
return DecryptStringFromBytes(Convert.FromBase64String(TextValue), keybytes, iv);
}
static string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.Mode = CipherMode.CBC;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt =
new CryptoStream(msDecrypt,decryptor,CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
I am producing different encrypted size strings:
JS:MhAP11fHa+fUfRzSw2UHVQ==
C#:+Ijpt1GDVgM4MqMAQUwf0Q==
I get Padding is invalid and cannot be removed, when trying to decrypt the JS string in c#
Where have I got wrong?.
Basically you run into encoding issues. First of all, you parse your IV using Base64 decoding in one implementation and one using direct character encoding in the other. Your Base64 strings don't look like Base64 strings either.
Furthermore, many libraries (incorrectly) allow that incorrect key and IV sizes are used. This is however confusing as there is no generic way for key or IV expansion. So you should make sure that the binary representations of the key and IV are correct for the specific algorithm.
For AES you should use a key size of 128, 192 or 256 bits and an IV size identical to the block size, 128 bits. The IV should be randomly generated and communicated to the other side, e.g. by prefixing the IV to the ciphertext.

ASP.NET membership salt?

How does ASP.NET membership generate their salt key and then how do they encode it (that is, is it salt + password or password + salt)?
I am using SHA-1 with my membership, but I would like to recreate the same salts so the built-in membership stuff could hash the stuff the same way as my stuff can.
Edit 2: Never mind. I misread it and was thinking it said bytes, not bits. So I was passing in 128 bytes, not 128 bits.
Edit: I been trying to make it so. This is what I have,
public string EncodePassword(string password, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(password);
byte[] src = Encoding.Unicode.GetBytes(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inArray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inArray);
}
private byte[] createSalt(byte[] saltSize)
{
byte[] saltBytes = saltSize;
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(saltBytes);
return saltBytes;
}
So I have not tried to see if the ASP.NET membership will recognize this yet the hashed password looks close. I just don't know how to convert it to base64 for the salt.
I did this
byte[] storeSalt = createSalt(new byte[128]);
string salt = Encoding.Unicode.GetString(storeSalt);
string base64Salt = Convert.ToBase64String(storeSalt);
int test = base64Salt.Length;
Test length is 172 which is well over the 128 bits so what am I doing wrong?
This is what their salt looks like
vkNj4EvbEPbk1HHW+K8y/A==
This is what my salt looks like
E9oEtqo0livLke9+csUkf2AOLzFsOvhkB/NocSQm33aySyNOphplx9yH2bgsHoEeR/aw/pMe4SkeDvNVfnemoB4PDNRUB9drFhzXOW5jypF9NQmBZaJDvJ+uK3mPXsWkEcxANn9mdRzYCEYCaVhgAZ5oQRnnT721mbFKpfc4kpI=
What is default hash algorithm that ASP.NET membership uses? has a good discussion of their default algorithm.
I hope that helps!
Edit-
The answer I was referring to is the code in the top post,
public string EncodePassword(string pass, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(pass);
//byte[] src = Encoding.Unicode.GetBytes(salt); Corrected 5/15/2013
byte[] src = Convert.FromBase64String(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inArray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inArray);
}
They are combining Unicode Salt + Pass using BlockCopy
-- In response to your question:
Both algorithms are necessary and fulfill different roles...
RNG Crypto is used to generate the salt. It is basically a long string of random data. This is generated and stored on a per user basis. Typically this is done when a user is created or a password is changed.
BlockCopy is just the method they use to combine the salt with the password. The above code essentially equates to salt + password.
You aren't going to be able to recreate a salt value as it is completely random. It is, however, stored for each user by the framework.
Combining the salt with the password and hashing it using the technique above will allow you to verify users passwords using the hashed value stored by the framework.
I think we both read your question differently. The code I posted won't generate your salt, but it will let you use it in a way that is compatible with ASP.NET membership.
Sorry my explanation isn't the best- does that answer your question?
Here is how the SQLMembershipProvider generates salt.
private string GenerateSalt() {
var buf = new byte[16];
(new RNGCryptoServiceProvider()).GetBytes(buf);
return Convert.ToBase64String(buf);
}
You can download the ASP.NET's SQL Provider code here.
The issue I was having was a development application running on IIS7. .NET 4.0 was using a different default hash algorithm than the default HashAlgorithmType for .NET 2.0.
In the "EncodePassword" sample code by Microsoft, they reference Membership.HashAlgorithmType which I believe returns the default for the framework, if it is not specified in the web.config.
I was able to get both this GenerateSalt and EncodePassword method to work for my application.
My combined code:
internal string GenerateSalt()
{
byte[] buf = new byte[16];
(new RNGCryptoServiceProvider()).GetBytes(buf);
return Convert.ToBase64String(buf);
}
internal string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bAll = new byte[bSalt.Length + bIn.Length];
byte[] bRet = null;
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm s = HashAlgorithm.Create("SHA1");
// Hardcoded "SHA1" instead of Membership.HashAlgorithmType
bRet = s.ComputeHash(bAll);
}
else
{
bRet = EncryptPassword(bAll);
}
return Convert.ToBase64String(bRet);
}
Here is one way of doing it. A salt is just a random number, you can use RNGCryptoServiceProvider class in the framework library to produce good random number to use as salt
private const int ITERATIONS = 10000;
private const int SALT_SIZE = 32;
private const int HASH_SIZE = 32;
public void SaltAndHashPassword(string password, out byte[] salt,
out byte[] hash)
{
Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
password,
SALT_SIZE,
ITERATIONS);
salt = rdb.Salt;
hash = rdb.GetBytes(HASH_SIZE);
}

Resources