I'm working on a project to decrypt an AES-128 encrypted string in ColdFusion that is passed as a URL parameter.
The vendor takes a pass phrase and converts it to a valid AES-128 key "using an algorithm equivalent to Microsoft's CryptDeriveKey using the SHA-1 hash function." I need to replicate this generatedKey in ColdFusion so I can use the value in my decrypt() call.
When using CryptDeriveKey you pass the encryption type, the Hash type, the block length and a 0 iv array and it returns the Hash. Source: Generating a Key from a Password
// generate an RC2 key
byte[] iv = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] key = cdk.CryptDeriveKey(“RC2”, “SHA1”, 128, iv);
In the vendor's test tool, the pass phrase "test1234" results in a Hash of:
A6455C7A24BC5E869B0DDF647238F5DA
I found the genAESKeyFromPW() UDF, which seems to be the closest, but requires a salt which CryptDeriveKey does not use. I have also tried the code below. However, it is not working as the Hash() is not creating a valid AES-128 key:
<cfset generatedKey = Hash('test1234', 'SHA-1')>
<cfset decrypted=decrypt(encryptedString, generatedKey, 'AES/CBC/PKCS7Padding', 'Base64', '0')>
What steps do I need to replicate CryptDeriveKey function?
Update:
The vendor provided this C# example of the decryption:
public static byte[] AesDecryptBytes(byte[] cipherText, byte[] key)
{
byte[] IV = new byte[16];
AesManaged aes = new AesManaged();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform decryptor = aes.CreateDecryptor(key, IV);
byte[] plainBytes;
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(cipherText, 0, cipherText.Length);
}
plainBytes = memoryStream.ToArray();
}
return plainBytes;
}
From what I have read in this documentation, it sounds like the function essentially hashes the password binary then does an XOR with two arrays. I am not 100%, but I do not think the method they described is the same as PBKDF1 or PBKDF2.
Let n be the required derived key length, in bytes. The derived key is
the first n bytes of the hash value after the hash computation has
been completed by CryptDeriveKey. If the hash is not a member of the
SHA-2 family and the required key is for either 3DES or AES, the key
is derived as follows:
Form a 64-byte buffer by repeating the constant 0x36 64 times. Let k be the length of the hash value that is represented by the input
parameter hBaseData. Set the first k bytes of the buffer to the result
of an XOR operation of the first k bytes of the buffer with the hash
value that is represented by the input parameter hBaseData.
Form a 64-byte buffer by repeating the constant 0x5C 64 times. Set the first k bytes of the buffer to the result of an XOR operation of
the first k bytes of the buffer with the hash value that is
represented by the input parameter hBaseData.
Hash the result of step 1 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData
parameter.
Hash the result of step 2 by using the same hash algorithm as that used to compute the hash value that is represented by the hBaseData
parameter.
Concatenate the result of step 3 with the result of step 4.
Use the first n bytes of the result of step 5 as the derived key.
CF Key Generation
Start by hashing the password and converting it into binary with binaryDecode:
hBaseData = binaryDecode(hash("test1234", "SHA1"), "hex");
Build and populate the two buffers with the specified constants:
// 0x36 (i.e. 54 decimal)
buff1 = listToArray(repeatString("54,", 64));
// 0x5C (i.e. 92 decimal)
buff2 = listToArray(repeatString("92,", 64));
Then do a bitwise XOR, storing the results in the buffer:
for (k = 1; k <= arrayLen(hBaseData); k++) {
buff1[k] = BitXOR( buff1[k], hBaseData[k]);
buff2[k] = BitXOR( buff2[k], hBaseData[k]);
}
Next hash() both buffers and concatenate the results:
hash1 = hash( javacast("byte[]", buff1), "SHA1");
hash2 = hash( javacast("byte[]", buff2), "SHA1");
combined = hash1 & hash2;
Finally, extract the first n bytes (16 == 128 bits / 8) as the new key. Since CF's hash() function returns hexadecimal (always two characters per byte) string functions can be used here.
keySize = 128 / 8;
newKey = left(combined, keySize *2);
Result: A6455C7A24BC5E869B0DDF647238F5DA
Decrypting in CF
Before you can decrypt, a few important notes:
CF's encrypt/decrypt functions expect keys to be encoded as base64. The generated key above is in hexadecimal format. So it must be converted first:
"PKCS7Padding" is not valid for CF/Java. Instead use PKCS5Padding.
"CBC" mode always requires an IV. The IV is a binary array whose length is the same as the algorithm's block size (AES block size = 16 bytes). It must be "...the same value [used to encrypt] to successfully decrypt the data." Based on the descriptions in your API, your IV should be all zeros. (This is not good in practice, but is okay for a test case).
For more details, see Strong encryption in ColdFusion
Example:
encrypted = "1lqcm0Jiy4Rs29tz2jpuoQ==";
newKeyHex = "A6455C7A24BC5E869B0DDF647238F5DA";
keyBase64 = binaryEncode(binaryDecode(newKeyHex, "hex"), "base64");
iv = javacast("byte[]", [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
decrypted = decrypt(encrypted, keyBase64, "AES/CBC/PKCS5Padding", "Base64", iv);
writeOutput("<br>"& decrypted);
Result: recordID=000001
Related
In the (PK)ZIP specification at https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT, specifically in the Strong Encryption Specification (SES) section, there is a line on deriving a key from a password:
MasterSessionKey = DeriveKey(SHA1(Password))
What's DeriveKey?
(In WinZip's AES documentation at https://www.winzip.com/en/support/aes-encryption/, they use PBKDF2 with 1000 iterations. I don't see any similar explanation in APPNOTE)
PKWARE implemented a strong encryption in version 5, but did not provide the algorithm of encoding/decoding (Method For Strongly Encrypted .ZIP Files - Patent US 2020/0250329 A1). In this algorithm AES encryption was implemented as part of it. You can define this by strong encryption (bit 6) = yes in General Purpose Flag.
After that WinZip could not use this algo, so it invented another one. You can define this by strong encryption (bit 6) = no in General Purpose Flag and AesExtraFieldRecord with signature 0x990.
As you can see there're two ways to encrypt a zip file. All open source software use the second one. The first one is available only by PKWARE SecureZIP
You can find example of this alogirthm in (7zip) Strong.cpp:35. In java it should look like this:
public static byte[] getMasterKey(String password) {
byte[] data = password.getBytes(StandardCharsets.UTF_8);
byte[] sha1 = DigestUtils.sha1(data);
return DeriveKey(sha1);
}
private static byte[] DeriveKey(byte[] digest) {
byte[] buf = new byte[kDigestSize * 2]; // kDigestSize = 20
DeriveKey2(digest, (byte)0x36, buf, 0);
DeriveKey2(digest, (byte)0x5C, buf, kDigestSize);
return Arrays.copyOfRange(buf, 0, 32);
}
private static void DeriveKey2(byte[] digest, byte c, byte[] dest, int offs) {
byte[] buf = new byte[64];
Arrays.fill(buf, c);
for (int i = 0; i < kDigestSize; i++)
buf[i] ^= digest[i];
byte[] sha1 = DigestUtils.sha1(buf);
System.arraycopy(sha1, 0, dest, offs, sha1.length);
}
Demo:
String password = "JohnDoe";
byte[] masterKey = getMasterKey(password);
The next paragraph 'defines' it
7.2.5.3 The function names and parameter requirements will depend on
the choice of the cryptographic toolkit selected. Almost any
toolkit supporting the reference implementations for each
algorithm can be used. The RSA BSAFE(r), OpenSSL, and Microsoft
CryptoAPI libraries are all known to work well.
I guess it's up to you to decide which of the encryption algorithms you want to use and go from there
I'm using .NET's implementation of RSA, and two things looked odd to me. I'd like to confirm that it's operating properly.
Background
Using System.Security.Cryptography.RSACryptoServiceProvider with 2048-bit keyword size to perform asymmetric encryption/decrpytion, initially following the example in this question, "AES 256 Encryption: public and private key how can I generate and use it .net".
As a first implementation, this seems to work:
public const int CSPPARAMETERS_FLAG = 1; // Specifies RSA: https://msdn.microsoft.com/en-us/library/ms148034(v=vs.110).aspx
public const bool USE_OAEP_PADDING = false;
public const int KEYWORD_SIZE = 2048;
public static byte[] Encrypt(byte[] publicKey, byte[] dataToEncrypt)
{
var cspParameters = new System.Security.Cryptography.CspParameters(CSPPARAMETERS_FLAG);
byte[] encryptedData = null;
using (var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(cspParameters))
{
try
{
rsaProvider.PersistKeyInCsp = false;
rsaProvider.ImportCspBlob(publicKey);
encryptedData = rsaProvider.Encrypt(dataToEncrypt, USE_OAEP_PADDING);
}
finally
{
rsaProvider.PersistKeyInCsp = false;
rsaProvider.Clear();
}
}
return encryptedData;
}
public static byte[] Decrypt(byte[] privateKey, byte[] dataToDecrypt)
{
var cspParameters = new System.Security.Cryptography.CspParameters(CSPPARAMETERS_FLAG);
byte[] encryptedData = null;
using (var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(cspParameters))
{
try
{
rsaProvider.PersistKeyInCsp = false;
rsaProvider.ImportCspBlob(privateKey);
encryptedData = rsaProvider.Decrypt(dataToDecrypt, USE_OAEP_PADDING);
}
finally
{
rsaProvider.PersistKeyInCsp = false;
rsaProvider.Clear();
}
}
return encryptedData;
}
After looking into these methods a bit more, it seems that the public key that I've been generating as from the example seemed to have a lot of very predictable data at its start, and it was 276-bytes long.
Apparently rsaProvider.ExportCspBlob(bool includePrivateParameters) is a functional alternative to rsaProvider.ExportParameters(bool includePrivateParameters); the main difference is that the blob is already serialized as a byte[] while the other emits the object version, RSAParameters.
Two observations about the methods:
The .Exponent is always 0x010001$=65537$.
The exported blobs contain 17 extra bytes versus the serialized typed versions.
rsaProvider.ExportCspBlob():
Public key is 276 bytes.
Private key is 1172 bytes.
RSAParameters:
Public key is 259 bytes.
.Exponent.Length = 3
.Modulus .Length = 256
Private key is 1155 bytes.
.D .Length = 256
.DP .Length = 128
.DQ .Length = 128
.Exponent.Length = 3
.InverseQ.Length = 128
.Modulus .Length = 256
.P .Length = 128
.Q .Length = 128
The extra 17 bytes appear to be at the header of the binary blob.
Concerns
From this, two concerns:
Is it okay for the exponent to not be random?
If the exponent is defined as a constant, then it'd seem like that's another 3 bytes I could shave off the serialization?
Another question, Should RSA public exponent be only in {3, 5, 17, 257 or 65537} due to security considerations?, seems to suggest that $\left{3, 5, 17, 257, 65537\right}$ are all common values for the exponent, so 0x101$=65537$ seems reasonable if it's true that there's no harm in always using the same constant exponent.
Are the 17 extra bytes an information leak?
Do they represent the option parameters like key length and method?
Is it a good idea to be transmitting option parameter information when I already know that both the sender and receiver are using the same, hard-coded method?
Question
Is RSACryptoServiceProvider's behavior a cause for concern, or are these things normal?
Update 1
In Should RSA public exponent be only in {3, 5, 17, 257 or 65537} due to security considerations?, the accepted answer starts off by noting:
There is no known weakness for any short or long public exponent for RSA, as long as the public exponent is "correct" (i.e. relatively prime to p-1 for all primes p which divide the modulus).
If this is so, then I'd guess that the apparently-constant exponent of 0x010001$=65537$ is sufficient as long as it's relatively prime to $p-1$. So, presumably the .NET implementation of RSA checks for this condition.
But then what does RSACryptoServiceProvider do if that condition isn't satisfied? If it selects a different exponent, then that'd seem to leak information about $p$ whenever the exponent isn't 0x010001. Or, if a different key is selected, then it'd seem like we can just assume that the exponent is always 0x010001 and omit it from the serialization.
Everything reported is normal, and non-alarming.
It is perfectly OK for the public exponent e to be short and non-random. e = 216+1 = 65537 = 0x010001 is common and safe. Some authorities mandate it (or some range including it). Using it (or/and something significantly larger than the bit size of the public modulus) gives some protection against some of the worst RSA paddings.
No, the 17 extra bytes in the public key are unlikely to be an information leak; they more likely are a header part of the data format chosen for an RSA public key by the software you use. My guess is that you are encountering the MS-specific format detailed in this answer (perhaps, within endianness), which also uses precisely 276 bytes for an RSA public key with a 2048-bit public modulus. In that case, you should find that the extra bytes are always the same (thus they demonstrably leak nothing). And there are countless more subtle ways to leak information about the private key, like in the public modulus itself.
Many RSA key generators used in practice, including I guess RSACryptoServiceProvider, first choose e, then somewhat avoid generating primes p such that gcd(e, p-1) ≠ 1. Since e = 65537 is prime, it is enough that ( p % e ) ≠ 1, and this is easily checked, or otherwise insured by the process generating p.
I just start learning Bouncy Castle for AES encryption/decryption. I am using AES/CBC/PKCS7PADDING with 256-bit key.
BC can encrypt and decrypt text successfully, however after decryption I notice that there are always a few padding of null (0x00), which therefore fails my hash comparison. For example, suppose original input string is “1234567890”, the decrypted byte array is always:
{0x49,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x48,0x00,0x00,0x00,0x00,0x00,0x00}
Why the padding is not 0x06,0x06,0x06,0x06,0x06,0x06? And is there any way to deterministically tell the padding length (could be 0) after encryption so that I can get exactly the same string before encryption?
When you specify PKCS7, BC will add the padding to the data before encrypting, and remove it again when decrypting. PKCS7 with AES would always add at least 1 byte of padding, and will add enough data to make the input a multiple of the AES block size. When decrypting the padding is verified to be correct, and in the case of PKCS7 also serve as an indicator of how much of the last block of decrypted data is padding, and how much is real data.
If you try decrypting the encrypted and padded data without specifying PKCS7 in the decrypt step, the padding would still be in the decrypted data.
Edit:
To illustrate my point .. here is some Java code that encrypts "1234567890" with AES/CBC/PKCS7, and then decrypts it again both with and without the PKCS7 padding:
public class BCTest {
public static void doTest() throws Exception {
Security.addProvider(new BouncyCastleProvider());
byte[] clearData = "1234567890".getBytes();
SecretKey secretKey = new SecretKeySpec("0123456789ABCDEF".getBytes(), "AES");
AlgorithmParameterSpec IVspec = new IvParameterSpec("0123456789ABCDEF".getBytes());
// encrypt with PKCS7 padding
Cipher encrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
encrypterWithPad.init(Cipher.ENCRYPT_MODE, secretKey, IVspec);
byte[] encryptedData = encrypterWithPad.doFinal(clearData);
System.out.println("Encryped data (" + encryptedData.length + " bytes): \t" + toHexString(encryptedData));
// decrypt with PKCS7 pad
Cipher decrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
decrypterWithPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
byte[] buffer1 = new byte[encryptedData.length];
int decryptLen1 = decrypterWithPad.doFinal(encryptedData, 0, encryptedData.length, buffer1);
System.out.println("Decrypted with Pad (" + decryptLen1 + " bytes): \t" + toHexString(buffer1));
// decrypt without PKCS7 pad
Cipher decrypterWithoutPad = Cipher.getInstance("AES/CBC/NOPADDING", "BC");
decrypterWithoutPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
byte[] buffer2 = new byte[encryptedData.length];
int decryptLen2 = decrypterWithoutPad.doFinal(encryptedData, 0, encryptedData.length, buffer2);
System.out.println("Decrypted without Pad (" + decryptLen2 + " bytes):\t" + toHexString(buffer2));
}
private static String toHexString(byte[] bytes) {
return javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
}
public static void main(String[] args) throws Exception {
BCTest.doTest();
}
}
Output:
Encryped data (16 bytes): 602CAE14358D0AC5C96E2D46D17E58E3
Decrypted with Pad (10 bytes): 31323334353637383930000000000000
Decrypted without Pad (16 bytes): 31323334353637383930060606060606
When decrypting with the padding option, the output have been striped of the padding - and the cipher indicates 10 bytes of decrypted data - the rest of the buffer is 0 filled. Decrypting without the padding option, results in the padding now being part of the decrypted data.
Edit2:
Now seeing the original code, confirms my hunch. The methode GetOutputSize don't return the output size of the decrypted string, but only the maximum needed space in an output buffer. The methode have the following documentation in the BC code:
/**
* return the size of the output buffer required for an update plus a
* doFinal with an input of len bytes.
*
* #param len the length of the input.
* #return the space required to accommodate a call to update and doFinal
* with len bytes of input.
*/
DoFinal returns the actual length of the decrypted data put in the buffer.
So in
byte[] plainTextBuffer = new byte[cipher.GetOutputSize(data.Length - IV_LENGTH)];
int length = cipher.DoFinal(data, iv.Length, data.Length - iv.Length, plainTextBuffer, 0);
The plainTextBuffer would be slightly larger than the actual decrypted data - the actual length of data would be in length.
i am using c# from bouncycastle. looks to me this might be a bug from bouncycastle, or at least bouncycastle c# implementation does not follow pkcs7 spec exactly.
my solution is to chop off the trailing bytes that are not included in the return length of DoFinal. still not very sure why there are padding of 0x00, which as said should not exist at all.
below is the code. i used AES/CBC/PKCS7PADDING for both encryption and decryption.
encryption --->
ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, StringToByteArray(IV_STRING));
byte[] iv = ((ParametersWithIV) aesIVKeyParam).GetIV();
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
cipher.Init(true, aesIVKeyParam);
byte[] cipherText = new byte[iv.Length + cipher.GetOutputSize(data.Length)];
Array.Copy(iv, 0, cipherText, 0, iv.Length);
int length = cipher.DoFinal(data, 0, data.Length, cipherText, iv.Length);
decryption --->
ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
byte[] iv = new byte[IV_LENGTH];
Array.Copy(data, 0, iv, 0, IV_LENGTH);
ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, iv);
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
cipher.Init(false, aesIVKeyParam);
byte[] plainTextBuffer = new byte[cipher.GetOutputSize(data.Length - IV_LENGTH)];
int length = cipher.DoFinal(data, iv.Length, data.Length - iv.Length, plainTextBuffer, 0);
How do you decrypt aes 128 ctr encrypted file from the middle for http range support?
Here is the encrypted file:
https://www.dropbox.com/s/8e9qembud6n3z7i/encrypted.txt?dl=0
the key is base64 encrypted: E7VQWj3cv1JUi5pklirtDQ9SRJt1DhiqYgzPSpIiVP0
Mega docs: https://mega.co.nz/#doc
The IV is calculated by decrypting the key which gives an array:
Array
(
[0] => 330649690
[1] => 1037877074
[2] => 1418435172
[3] => 2519395597
[4] => 257049755
[5] => 1963858090
[6] => 1645006666
[7] => 2451723517
)
The IV is obtained by slicing the array at 4th offset with length of two And the last two elements of the array are filled with 0:
Array
(
[0] => 257049755
[1] => 1963858090
[2] => 0
[3] => 0
)
Then the key is XOR'd and made into a 128bit array which is then converted into string by the php function pack:
$key = array($key[0] ^ $key[4], $key[1] ^ $key[5], $key[2] ^ $key[6], $key[3] ^ $key[7]);
$key = base64_encode(a32_to_str($key));
$iv = base64_encode(a32_to_str($iv));
Then the file is decrypted using the normal php aes library. I am using mcrypt_generic for the decryption process.
The problem arises when I try to decrypt the file from 2nd byte or the 3rd or the middle.
It works fine if I decrypt it from the 1st byte.
Another thing I have noticed is, If I decrypt the file from 2nd byte, but before that, I decrypt a random string or just the digit 0, the decryption works from the 2nd byte then.
I suppose it has something to do with the IV block counter. I decrypt a random byte then continue decrypting the actual cipher so it works.
I need to start decrypting the file from the start, lets say from the 40mb offset to support live strem seeking.
But that would consume too much memory because I will have to decrypt 40mb of 0's before seeking can be done.
How can I move the IV counter value to 40mb offset ??
I read that IV is increased by +1 for each block for decryption. But since my IV is an array I have tried everything it does not work if I add 1 in it.
I've been at it for months with no fruit. Please help
Here is my previous question which helped understanding the process a bit: AES 128 bit CTR partial file decryption with PHP
Your initial research is indeed correct. In CTR mode, the IV (or nonce) is simply incremented by 1 after each encryption operation. (Encryption and decryption are the same operation in CTR mode, so you can substitute one word for the other as necessary.)
In other words, the state of a CTR mode cipher can be predicted in advance – just add the number of blocks already encrypted to the initial IV. In particular, the state does not depend on the plaintext in any way. AES has a block size of 16, so you would add the number of bytes encrypted divided by 16.
The IV can be considered a 128-bit integer stored in big endian. The cryptography API you use represents it as an array of four 32-bit integers. Simply add the number of blocks to the fourth integer before initializing the cipher. If you think you'll need to handle more than four billion blocks or so, you need to add handling for overflow to the third integer.
The slightly trickier part is initializing the cipher to a state where you have already encrypted a number of bytes that is not divisible by the block size. The solution is to first initialize the cipher to the number of bytes already encrypted divided by 16, rounded down, and then encrypting (the number of bytes already encrypted mod 16) dummy bytes. I believe this is in fact what you already suspected.
You're writing in PHP, but I'm posting a method from a Mega downloader program which I've written in Java in case it helps:
public Cipher getDownloadCipher(final long startPosition) throws Exception {
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
final ByteBuffer buffer = ByteBuffer.allocate(16).put(nonce);
buffer.asLongBuffer().put(startPosition / 16);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(buffer.array()));
final int skip = (int) (startPosition % 16);
if (skip != 0) {
if (cipher.update(new byte[skip]).length != skip) {
//that should always work with a CTR mode cipher
throw new IOException("Failed to skip bytes from cipher");
}
}
return cipher;
}
We have a current application where user login credentials are stored in a SQL Server DB. These are, basically, stored as a plain text username, a password hash, and an associated salt for this hash.
These were all created by built in functions in ASP.NET's membership/role system. Here's a row for a user named 'joe' and a password of 'password':
joe,kDP0Py2QwEdJYtUX9cJABg==,OJF6H4KdxFLgLu+oTDNFodCEfMA=
I've dumped this stuff into a CSV file and I'm attempting to get it into a usable format for Django which stores its passwords in this format:
[algo]$[salt]$[hash]
Where the salt is a plain string and the hash is the hex digest of an SHA1 hash.
So far I've been able to ascertain that ASP is storing these hashes and salts in a base64 format. Those values above decode into binary strings.
We've used reflector to glean how ASP authenticates against these values:
internal string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0)
{
return pass;
}
byte[] bytes = Encoding.Unicode.GetBytes(pass);
byte[] src = Convert.FromBase64String(salt);
byte[] dst = new byte[src.Length + bytes.Length];
byte[] inArray = null;
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
if (passwordFormat == 1)
{
HashAlgorithm algorithm = HashAlgorithm.Create(Membership.HashAlgorithmType);
if ((algorithm == null) && Membership.IsHashAlgorithmFromMembershipConfig)
{
RuntimeConfig.GetAppConfig().Membership.ThrowHashAlgorithmException();
}
inArray = algorithm.ComputeHash(dst);
}
else
{
inArray = this.EncryptPassword(dst);
}
return Convert.ToBase64String(inArray);
}
Eseentially, pulls in the salt from the DB and b64 decodes it into a binary representation. It does a "GetBytes" on the raw password and then it concatinates them, salt first.
It then runs the SHA1 algorithm on this new string, base64 encodes it, and compares it against the value stored in the database.
I've attempted to write some code to try and reproduce these hashes in Python and I'm failing. I won't be able to use them in Django until I can figure out how this translates over. Here's how I'm testing:
import hashlib
from base64 import b64decode, b64encode
b64salt = "kDP0Py2QwEdJYtUX9cJABg=="
b64hash = "OJF6H4KdxFLgLu+oTDNFodCEfMA="
binsalt = b64decode(b64salt)
password_string = 'password'
m1 = hashlib.sha1()
# Pass in salt
m1.update(binsalt)
# Pass in password
m1.update(password_string)
# B64 encode the binary digest
if b64encode(m1.digest()) == b64hash:
print "Logged in!"
else:
print "Didn't match"
print b64hash
print b64encode(m1.digest())
I'm wondering if anyone can see any flaws in my approach or can suggest an alternate method. Perhaps you can take the algorithms above and the known password and salt above and produce the hash on your system?
It appears python is inserting a byte order marker when you convert a UTF16 string to binary. The .NET byte array contains no BOM, so I did some ghetto python that turns the UTF16 into hex, removes the first 4 characters, then decodes it to binary.
There may be a better way to rip out the BOM, but this works for me!
Here's one that passes:
import hashlib
from base64 import b64decode, b64encode
def utf16tobin(s):
return s.encode('hex')[4:].decode('hex')
b64salt = "kDP0Py2QwEdJYtUX9cJABg=="
b64hash = "OJF6H4KdxFLgLu+oTDNFodCEfMA="
binsalt = b64decode(b64salt)
password_string = 'password'.encode("utf16")
password_string = utf16tobin(password_string)
m1 = hashlib.sha1()
# Pass in salt
m1.update(binsalt + password_string)
# Pass in password
# B64 encode the binary digest
if b64encode(m1.digest()) == b64hash:
print "Logged in!"
else:
print "Didn't match"
print b64hash
print b64encode(m1.digest())
Two thoughts as to what could be going wrong.
First the code from the reflection has three paths:
If passwordFormat is 0 it returns the password as is.
If passwordFormat is 1 it creates the hash as your python code does.
If passwordFormat is anything other than 0 or 1 it calls this.EncryptPassword()
How do you know you are hashing the password, and not encrypting the password with this.EncryptPassword()? You may need to reverse the EncryptPassword() member function and replicate that. That is unless you have some information which ensures that you are hashing the password and not encrypting it.
Second if it is indeed hashing the password you may want to see what the Encoding.Unicode.GetBytes() function returns for the string "password", as you may be getting something back like:
0x00 0x70 0x00 0x61 0x00 0x73 0x00 0x73 0x00 0x77 0x00 0x6F 0x00 0x72 0x00 0x64
instead of:
0x70 0x61 0x73 0x73 0x77 0x6F 0x72 0x64
I hope this helps.