I was wondering if anyone have tried to do an equivalent of
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(secret, saltValueBytes);
byte[] secretKey = key.GetBytes(16);
in Java. Where secret is a string(password), and saltValueBytes is, well, a salt in byte array.
I've tried stuff, but can't seem to wrap my head around it.
I found this implementation by means of a Google search but I have never used it.
A free Java implementation of RFC 2898
/ PKCS#5 PBKDF2
There seems to be no small and freely
available Java implementation of RFC
2898 / PKCS#5 available. Small as in
only a few source files, with trivial
compile and no dependencies, free as
in LGPL.
Given the availability of HMacSHA1 in
the standard SUN JCE cryptographic
provider, such an implementation is
quite simple and can be derived from
the RFC description quite literally.
My code is a clean-room implementation
with only the RFC as its basis.
I know this is late to the game, but Java 6 and up does have a built-in PBKDF2 implementation.
int dkLen = 64;
int rounds = 1000;
PBEKeySpec keySpec = new PBEKeySpec("Some password".toCharArray(), "SomeSalt".getBytes(), rounds, dkLen * 8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] out = factory.generateSecret(keySpec).getEncoded();
The list of Java 6 Security Enhancements claims comparability with PKCS#5, and through my own (cursory) testing it does appear to produce correct PBKDF2 keys.
This one works for me.
I still am in disbelief that a standard implementation of an RFC2898-compliant PBKDF2 is not present in the JRE. I think I must be looking in the wrong places. The name confusion (RFC2898 PKCS5 PBKDF2) doesn't help.
// PBKDF2.java
// ------------------------------------------------------------------
//
// RFC2898 PBKDF2 in Java. The RFC2898 defines a standard algorithm for
// deriving key bytes from a text password. This is also called
// "PBKDF2", for Password-based key derivation function #2.
//
// There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I
// know, but it is available in many J2EE runtimes, including those from
// JBoss, IBM, and Oracle.
//
// It's fairly simple to implement, so here it is.
//
// Author: Admin
// built on host: DINOCH-2
// Created Sun Aug 09 01:06:57 2009
//
// last saved:
// Time-stamp: <2009-August-09 11:11:47>
// ------------------------------------------------------------------
//
// code from Matthias Gartner
//
// ------------------------------------------------------------------
package cheeso.examples;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class PBKDF2
{
public static byte[] deriveKey( byte[] password,
byte[] salt,
int iterationCount,
int dkLen )
throws java.security.NoSuchAlgorithmException,
java.security.InvalidKeyException
{
SecretKeySpec keyspec = new SecretKeySpec( password, "HmacSHA1" );
Mac prf = Mac.getInstance( "HmacSHA1" );
prf.init( keyspec );
// Note: hLen, dkLen, l, r, T, F, etc. are horrible names for
// variables and functions in this day and age, but they
// reflect the terse symbols used in RFC 2898 to describe
// the PBKDF2 algorithm, which improves validation of the
// code vs. the RFC.
//
// hLen denotes the length in octets of the pseudorandom function output
// dklen the length in octets (bytes) of the derived key.
int hLen = prf.getMacLength(); // 20 for SHA1
int l = Math.ceil( dkLen/hLen ); // 1 for 128bit (16-byte) keys
int r = dkLen - (l-1)*hLen; // 16 for 128bit (16-byte) keys
byte T[] = new byte[l * hLen];
int ti_offset = 0;
for (int i = 1; i <= l; i++) {
F( T, ti_offset, prf, salt, iterationCount, i );
ti_offset += hLen;
}
if (r < hLen) {
// Incomplete last block
byte DK[] = new byte[dkLen];
System.arraycopy(T, 0, DK, 0, dkLen);
return DK;
}
return T;
}
private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) {
final int hLen = prf.getMacLength();
byte U_r[] = new byte[ hLen ];
// U0 = S || INT (i);
byte U_i[] = new byte[S.length + 4];
System.arraycopy( S, 0, U_i, 0, S.length );
INT( U_i, S.length, blockIndex );
for( int i = 0; i < c; i++ ) {
U_i = prf.doFinal( U_i );
xor( U_r, U_i );
}
System.arraycopy( U_r, 0, dest, offset, hLen );
}
private static void xor( byte[] dest, byte[] src ) {
for( int i = 0; i < dest.length; i++ ) {
dest[i] ^= src[i];
}
}
private static void INT( byte[] dest, int offset, int i ) {
dest[offset + 0] = (byte) (i / (256 * 256 * 256));
dest[offset + 1] = (byte) (i / (256 * 256));
dest[offset + 2] = (byte) (i / (256));
dest[offset + 3] = (byte) (i);
}
// ctor
private PBKDF2 () {}
}
Slightly improved Cheeso's code for working with HMacSHA256 or HMacSHA512 by adding overloaded deriveKey(). With this change, the code was run with PKDF2-HMAC-SHA512 test vectors from
PHP Crypt lib which resulted in 6 failures out of 100 test cases.
// PBKDF2.java
// ------------------------------------------------------------------
//
// RFC2898 PBKDF2 in Java. The RFC2898 defines a standard algorithm for
// deriving key bytes from a text password. This is also called
// "PBKDF2", for Password-based key derivation function #2.
//
// There's no RFC2898-compliant PBKDF2 function in the JRE, as far as I
// know, but it is available in many J2EE runtimes, including those from
// JBoss, IBM, and Oracle.
//
// It's fairly simple to implement, so here it is.
//
// Author: Admin
// built on host: DINOCH-2
// Created Sun Aug 09 01:06:57 2009
//
// last saved:
// Time-stamp: <2009-August-09 11:11:47>
// ------------------------------------------------------------------
//
// code from Matthias Gartner
//
// ------------------------------------------------------------------
package cheeso.examples;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class PBKDF2
{
public static byte[] deriveKey(byte[] password,
byte[] salt,
int iterationCount,
int dkLen)
throws NoSuchAlgorithmException,
InvalidKeyException
{
return deriveKey("HmacSHA1", password, salt, iterationCount, dkLen);
}
public static byte[] deriveKey(String hmacAlgo,
byte[] password,
byte[] salt,
int iterationCount,
int dkLen)
throws NoSuchAlgorithmException,
InvalidKeyException
{
SecretKeySpec keyspec = new SecretKeySpec(password, hmacAlgo);
Mac prf = Mac.getInstance(hmacAlgo);
prf.init( keyspec );
// Note: hLen, dkLen, l, r, T, F, etc. are horrible names for
// variables and functions in this day and age, but they
// reflect the terse symbols used in RFC 2898 to describe
// the PBKDF2 algorithm, which improves validation of the
// code vs. the RFC.
//
// dklen is expressed in bytes. (16 for a 128-bit key, 32 for 256)
int hLen = prf.getMacLength(); // 20 for SHA1
int l = Math.max( dkLen, hLen); // 1 for 128bit (16-byte) keys
int r = dkLen - (l-1)*hLen; // 16 for 128bit (16-byte) keys
byte T[] = new byte[l * hLen];
int ti_offset = 0;
for (int i = 1; i <= l; i++) {
F( T, ti_offset, prf, salt, iterationCount, i );
ti_offset += hLen;
}
if (r < hLen) {
// Incomplete last block
byte DK[] = new byte[dkLen];
System.arraycopy(T, 0, DK, 0, dkLen);
return DK;
}
return T;
}
private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) {
final int hLen = prf.getMacLength();
byte U_r[] = new byte[ hLen ];
// U0 = S || INT (i);
byte U_i[] = new byte[S.length + 4];
System.arraycopy( S, 0, U_i, 0, S.length );
INT( U_i, S.length, blockIndex );
for( int i = 0; i < c; i++ ) {
U_i = prf.doFinal( U_i );
xor( U_r, U_i );
}
System.arraycopy( U_r, 0, dest, offset, hLen );
}
private static void xor( byte[] dest, byte[] src ) {
for( int i = 0; i < dest.length; i++ ) {
dest[i] ^= src[i];
}
}
private static void INT( byte[] dest, int offset, int i ) {
dest[offset + 0] = (byte) (i / (256 * 256 * 256));
dest[offset + 1] = (byte) (i / (256 * 256));
dest[offset + 2] = (byte) (i / (256));
dest[offset + 3] = (byte) (i);
}
// ctor
private PBKDF2 () {}
}
Related
I am trying to decrypt a ciphersaber encrypted hexadecimal message using an IV mixing round of 20 with the key MyKey.
The messages is:
bad85d9e7f5aff959b6b332b44af2cc554d8a6eb
I am doing this in pure C# and it should return the message: Hola Mundo
using System;
using System.Text;
public class Program
{
public static void Main(string[] args)
{
// Hexadecimal text
string hexText = "bad85d9e7f5aff959b6b332b44af2cc554d8a6eb";
// Convert hexadecimal text to byte array
byte[] encryptedData = new byte[hexText.Length / 2];
for (int i = 0; i < encryptedData.Length; i++)
{
encryptedData[i] = Convert.ToByte(hexText.Substring(i * 2, 2), 16);
}
// IV length
int ivLength = 1;
// Key loop iterations
int keyIterations = 20;
// Encryption key
string encryptionKey = "MyKey";
// Convert encryption key to byte array
byte[] keyData = Encoding.UTF8.GetBytes(encryptionKey);
// Create an array to store the IV
byte[] ivData = new byte[ivLength];
// Copy the first `ivLength` bytes of the encrypted data to the IV array
Array.Copy(encryptedData, 0, ivData, 0, ivLength);
// Create an array to store the encrypted message
byte[] messageData = new byte[encryptedData.Length - ivLength];
// Copy the remaining bytes of the encrypted data to the message data array
Array.Copy(encryptedData, ivLength, messageData, 0, messageData.Length);
// Create an array to store the decrypted message
byte[] decryptedData = new byte[messageData.Length];
// Perform the decryption
for (int i = 0; i < messageData.Length; i++)
{
decryptedData[i] = (byte)(messageData[i] ^ keyData[i % keyData.Length]);
for (int j = 0; j < keyIterations; j++)
{
decryptedData[i] = (byte)(decryptedData[i] ^ ivData[j % ivData.Length]);
}
}
// Convert the decrypted data to a string and print it
string decryptedMessage = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine("Decrypted message: " + decryptedMessage);
}
}
Now when I try it returns: �$�#���Jf=�I���
What mistake am I making in the code or am I implementing it wrong?
I tested the text with the following site to see if it was ok: https://ruletheweb.co.uk/cgi-bin/saber.cgi
CipherSaber uses as IV the first 10 bytes of the encrypted message. The rest is the actual ciphertext. The IV is appended to the key (giving the key setup input), which is used as input to the CipherSaber key setup, see CipherSaber, Technical description, 1st section.
In the posted code, an IV length of 1 is applied instead of 10, which incorrectly determines IV (and thus key setup input) and actual ciphertext. The correct determination of IV and actual ciphertext is:
private static (byte[], byte[]) SeparateIvCiphertext(byte[] ivCiphertext)
{
int ivLen = 10;
byte[] iv = new byte[ivLen];
Buffer.BlockCopy(ivCiphertext, 0, iv, 0, iv.Length);
byte[] ciphertext = new byte[ivCiphertext.Length - iv.Length];
Buffer.BlockCopy(ivCiphertext, iv.Length, ciphertext, 0, ciphertext.Length);
return (iv, ciphertext);
}
and of the key setup input:
private static byte[] GetKeySetupInput(byte[] key, byte[] iv)
{
byte[] keySetupInput = new byte[key.Length + iv.Length];
Buffer.BlockCopy(key, 0, keySetupInput, 0, key.Length);
Buffer.BlockCopy(iv, 0, keySetupInput, key.Length, iv.Length);
return keySetupInput;
}
Furthermore, the decryption itself seems to be implemented incorrectly or at least incompletely. CipherSaber uses RC4 as its encryption/decryption algorithm, which can be divided into a key setup and the actual encryption/decryption:
The referenced website performs decryption using CipherSaber-2. Compared to the original CipherSaber (referred to as CipherSaber-1), a modified key setup is used in which the CipherSaber-1/RC4 key setup is repeated multiple times, 20 times in the case of the posted data.
A description of the CipherSaber-1/RC4 key setup can be found here, Key-scheduling algorithm (KSA), a possible implementation for CipherSaber-2 is:
private static byte[] sBox = new byte[256];
private static void KeySetup(byte[] input, int iterations)
{
for (int i = 0; i < 256; i++)
{
sBox[i] = (byte)i;
}
int j = 0;
for (int cs2loop = 0; cs2loop < iterations; cs2loop++) // CipherSaber-2 modification
{
for (int i = 0; i < 256; i++)
{
j = (j + sBox[i] + input[i % input.Length]) % 256;
Swap(ref sBox[i], ref sBox[j]);
}
}
}
private static void Swap(ref byte val1, ref byte val2)
{
if (val1 == val2) return;
val1 = (byte)(val1 ^ val2);
val2 = (byte)(val2 ^ val1);
val1 = (byte)(val1 ^ val2);
}
The loop marked CipherSaber-2 modification in the code snippet is the modification compared to CipherSaber-1/RC4!
The actual encryption/decryption is described here, Pseudo-random generation algorithm (PRGA), a possible implememtation is:
private static byte[] Process(byte[] input)
{
int i = 0, j = 0;
byte[] result = new byte[input.Length];
for (int k = 0; k < input.Length; k++)
{
i = (i + 1) % 256;
j = (j + sBox[i]) % 256;
Swap(ref sBox[i], ref sBox[j]);
result[k] = (byte)(sBox[(sBox[i] + sBox[j]) % 256] ^ input[k]);
}
return result;
}
Note that this algorithm is used for both encryption and decryption.
With this, the posted encrypted message can be decrypted as follows:
using System;
using System.Text;
...
byte[] key = Encoding.UTF8.GetBytes("MyKey");
byte[] encryptedData = Convert.FromHexString("bad85d9e7f5aff959b6b332b44af2cc554d8a6eb");
(byte[] iv, byte[] ciphertext) = SeparateIvCiphertext(encryptedData);
byte[] keySetupInput = GetKeySetupInput(key, iv);
int iterations = 20;
KeySetup(keySetupInput, iterations);
byte[] plaintext = Process(ciphertext);
Console.WriteLine(Encoding.UTF8.GetString(plaintext)); // Hola Mundo
which gives Hola Mundo as plaintext.
How do I implement AES encryption and decryption in LabVIEW and configure the following settings?
Padding = PaddingMode.PKCS7
Mode = CipherMode.CBC
Key Size = 128
Block Size = 128
I have tried few option over here Igor Titov, AES Crypto Toolkit by Alab Technologies
Tried to reach both parties to confirm if those toolkits support above configuration, but they don't respond on phone or email.
Any help is greatly appreciated.
I found this code from Igor Titov
Encrypt:https://github.com/IgorTitov/LabVIEW-Advanced-Encryption-Standard/blob/master/Encrypt%20with%20AES.vi
/**
* Encrypt a text using AES encryption in Counter mode of operation
*
* Unicode multi-byte character safe
*
* #param plaintext Source text to be encrypted
* #param password The password to use to generate a key
* #param nBits Number of bits to be used in the key (128, 192, or 256)
* #returns Encrypted text
*/
public function encrypt(plaintext : String, password : String, nBits : int) : String //Done in LV
{
var blockSize : int = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits == BIT_KEY_128 || nBits == BIT_KEY_192 || nBits == BIT_KEY_256))
{
// standard allows 128/192/256 bit keys
throw new Error("Must be a key mode of either 128, 192, 256 bits");
}
plaintext = Utf8.encode(plaintext);
password = Utf8.encode(password);
// use AES itself to encrypt password to get cipher key (using plain password as source for key
// expansion) - gives us well encrypted key
var nBytes : int = nBits / 8; // no bytes in key
var pwBytes : Array = new Array(nBytes);
for (var i : int = 0;i < nBytes;i++)
{
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key : Array = cipher(pwBytes, keyExpansion(pwBytes)); // gives us 16-byte key
key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
// initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
// block counter in 2nd 8 bytes
var counterBlock : Array = new Array(blockSize);
var nonce : int = 123456789;////DEBUG!!!(new Date()).getTime(); // timestamp: milliseconds since 1-Jan-1970
var nonceSec : int = Math.floor(nonce / 1000);
var nonceMs : int = nonce % 1000;
// encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
for (i = 0;i < 4;i++)
{
counterBlock[i] = (nonceSec >>> (i * 8)) & 0xff;
}
for (i = 0;i < 4;i++)
{
counterBlock[i + 4] = nonceMs & 0xff;
}
// and convert it to a string to go on the front of the ciphertext
var ctrTxt : String = '';
for (i = 0;i < 8;i++)
{
ctrTxt += String.fromCharCode(counterBlock[i]);
}
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
var keySchedule : Array = keyExpansion(key);
var blockCount : int = Math.ceil(plaintext.length / blockSize);
var ciphertxt : Array = new Array(blockCount); // ciphertext as array of strings
for (var b : int = 0;b < blockCount;b++)
{
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
for (var c : int = 0;c < 4;c++)
{
counterBlock[15 - c] = (b >>> (c * 8)) & 0xff;
}
for (c = 0;c < 4;c++)
{
counterBlock[15 - c - 4] = (b / 0x100000000 >>> c * 8);
}
var cipherCntr : Array = cipher(counterBlock, keySchedule); // -- encrypt counter block --
// block size is reduced on final block
var blockLength : int = b < blockCount - 1 ? blockSize : (plaintext.length - 1) % blockSize + 1;
var cipherChar : Array = new Array(blockLength);
for (i = 0;i < blockLength;i++)
{
// -- xor plaintext with ciphered counter char-by-char --
cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b * blockSize + i);
//trace("i=",i,"plaintext.charCodeAt(b * blockSize + i)",plaintext.charCodeAt(b * blockSize + i),"cipherChar[i]=",cipherChar[i]);
cipherChar[i] = String.fromCharCode(cipherChar[i]);
}
ciphertxt[b] = cipherChar.join('');
//trace(ciphertxt);
}
// Array.join is more efficient than repeated string concatenation in IE
var ciphertext : String = ctrTxt + ciphertxt.join('');
//trace("before 64 encode:",ciphertext);
ciphertext = Base64.encode(ciphertext); // encode in base64
//trace("after 64 encode:",ciphertext);
//alert((new Date()) - t);
return ciphertext;
}
Decrypt: https://github.com/IgorTitov/LabVIEW-Advanced-Encryption-Standard/blob/master/Decrypt%20with%20AES.vi
/**
* Decrypt a text encrypted by AES in counter mode of operation
*
* #param ciphertext Source text to be encrypted
* #param password The password to use to generate a key
* #param nBits Number of bits to be used in the key (128, 192, or 256)
* #returns Decrypted text
*/
public function decrypt(ciphertext : String, password : String, nBits : int) : String
{
var blockSize : int = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
if (!(nBits == BIT_KEY_128 || nBits == BIT_KEY_192 || nBits == BIT_KEY_256)) {
// standard allows 128/192/256 bit keys
throw new Error("Must be a key mode of either 128, 192, 256 bits");
}
ciphertext = Base64.decode(ciphertext.split("\n").join(""));
password = Utf8.encode(password);
//var t = new Date(); // timer
// use AES to encrypt password (mirroring encrypt routine)
var nBytes : int = nBits / 8; // no bytes in key
var pwBytes : Array = new Array(nBytes);
for (var i : int = 0;i < nBytes;i++)
{
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
var key : Array = cipher(pwBytes, keyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
// recover nonce from 1st 8 bytes of ciphertext
var counterBlock : Array = new Array(8);
var ctrTxt : String = ciphertext.slice(0, 8);
for (i = 0;i < 8;i++)
{
counterBlock[i] = ctrTxt.charCodeAt(i);
}
// generate key schedule
var keySchedule : Array = keyExpansion(key);
// separate ciphertext into blocks (skipping past initial 8 bytes)
var nBlocks : int = Math.ceil((ciphertext.length - 8) / blockSize);
var ct : Array = new Array(nBlocks);
for (b = 0;b < nBlocks;b++)
{
ct[b] = ciphertext.slice(8 + b * blockSize, 8 + b * blockSize + blockSize);
//trace("ct[b]=",ct[b],"blockSize=",blockSize,8 + b * blockSize, 8 + b * blockSize + blockSize);
}
//var temp:String=ct[1];
// for (var i:int=0;i<temp.length;i++)
// {
// trace("ct[1]Byte Array:",temp.charCodeAt(i));
// }
var ciphertextArr : Array = ct; // ciphertext is now array of block-length strings
// plaintext will get generated block-by-block into array of block-length strings
var plaintxt : Array = new Array(ciphertextArr.length);
for (var b : int = 0;b < nBlocks;b++)
{
// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
for (var c : int = 0;c < 4;c++)
{
counterBlock[15 - c] = ((b) >>> c * 8) & 0xff;
}
for (c = 0;c < 4;c++)
{
counterBlock[15 - c - 4] = (((b + 1) / 0x100000000 - 1) >>> c * 8) & 0xff;
}
//trace(counterBlock);
var cipherCntr : Array = cipher(counterBlock, keySchedule); // encrypt counter block
//trace(cipherCntr);
var plaintxtByte : Array = new Array(String(ciphertextArr[b]).length);
for (i = 0;i < String(ciphertextArr[b]).length;i++)
{
// -- xor plaintxt with ciphered counter byte-by-byte --
plaintxtByte[i] = cipherCntr[i] ^ String(ciphertextArr[b]).charCodeAt(i);
//trace("i=",i,"plaintxtByte[i]=",plaintxtByte[i],"cipherCntr[i]=",cipherCntr[i],"String(ciphertextArr[b]).charCodeAt(i)=",String(ciphertextArr[b]).charCodeAt(i));
//trace(plaintxtByte[i]);
plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
}
plaintxt[b] = plaintxtByte.join('');
}
// join array of blocks into single plaintext string
var plaintext : String = plaintxt.join('');
plaintext = Utf8.decode(plaintext); // decode from UTF8 back to Unicode multi-byte chars
return plaintext;
}
Not sure what programming language is this. If I can get help in converting this code to C# that will solve my blocker.
Here is the VI snippet.
I did something similar with blowfish but am not familiar with the AES implementation by Alab Tech.
Assuming the Alab Tech library function correctly, just pad your data as necessary before encrypting it.
KCS7 (described in RFC 5652). This pads data to the blocksize with the
number that is equal to the number of added bytes. If the original
data is an integer multiple of N bytes, then an extra block of bytes
with value N is added
That sounds relatively straightforward and a simple block diagram as below shows how it might work:
I am trying to port AES GCM implementation in python OpenTLS project, to C# (.Net). Below is the code in OpenTLS code:
#######################
### Galois Counter Mode
#######################
class AES_GCM:
def __init__(self, keys, key_size, hash):
key_size //= 8
hash_size = hash.digest_size
self.client_AES_key = keys[0 : key_size]
self.server_AES_key = keys[key_size : 2*key_size]
self.client_IV = keys[2*key_size : 2*key_size+4]
self.server_IV = keys[2*key_size+4 : 2*key_size+8]
self.H_client = bytes_to_int(AES.new(self.client_AES_key, AES.MODE_ECB).encrypt('\x00'*16))
self.H_server = bytes_to_int(AES.new(self.server_AES_key, AES.MODE_ECB).encrypt('\x00'*16))
def GF_mult(self, x, y):
product = 0
for i in range(127, -1, -1):
product ^= x * ((y >> i) & 1)
x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000)
return product
def H_mult(self, H, val):
product = 0
for i in range(16):
product ^= self.GF_mult(H, (val & 0xFF) << (8 * i))
val >>= 8
return product
def GHASH(self, H, A, C):
C_len = len(C)
A_padded = bytes_to_int(A + b'\x00' * (16 - len(A) % 16))
if C_len % 16 != 0:
C += b'\x00' * (16 - C_len % 16)
tag = self.H_mult(H, A_padded)
for i in range(0, len(C) // 16):
tag ^= bytes_to_int(C[i*16:i*16+16])
tag = self.H_mult(H, tag)
tag ^= bytes_to_int(nb_to_n_bytes(8*len(A), 8) + nb_to_n_bytes(8*C_len, 8))
tag = self.H_mult(H, tag)
return tag
def decrypt(self, ciphertext, seq_num, content_type, debug=False):
iv = self.server_IV + ciphertext[0:8]
counter = Counter.new(nbits=32, prefix=iv, initial_value=2, allow_wraparound=False)
cipher = AES.new(self.server_AES_key, AES.MODE_CTR, counter=counter)
plaintext = cipher.decrypt(ciphertext[8:-16])
# Computing the tag is actually pretty time consuming
if debug:
auth_data = nb_to_n_bytes(seq_num, 8) + nb_to_n_bytes(content_type, 1) + TLS_VERSION + nb_to_n_bytes(len(ciphertext)-8-16, 2)
auth_tag = self.GHASH(self.H_server, auth_data, ciphertext[8:-16])
auth_tag ^= bytes_to_int(AES.new(self.server_AES_key, AES.MODE_ECB).encrypt(iv + '\x00'*3 + '\x01'))
auth_tag = nb_to_bytes(auth_tag)
print('Auth tag (from server): ' + bytes_to_hex(ciphertext[-16:]))
print('Auth tag (from client): ' + bytes_to_hex(auth_tag))
return plaintext
def encrypt(self, plaintext, seq_num, content_type):
iv = self.client_IV + os.urandom(8)
# Encrypts the plaintext
plaintext_size = len(plaintext)
counter = Counter.new(nbits=32, prefix=iv, initial_value=2, allow_wraparound=False)
cipher = AES.new(self.client_AES_key, AES.MODE_CTR, counter=counter)
ciphertext = cipher.encrypt(plaintext)
# Compute the Authentication Tag
auth_data = nb_to_n_bytes(seq_num, 8) + nb_to_n_bytes(content_type, 1) + TLS_VERSION + nb_to_n_bytes(plaintext_size, 2)
auth_tag = self.GHASH(self.H_client, auth_data, ciphertext)
auth_tag ^= bytes_to_int(AES.new(self.client_AES_key, AES.MODE_ECB).encrypt(iv + b'\x00'*3 + b'\x01'))
auth_tag = nb_to_bytes(auth_tag)
# print('Auth key: ' + bytes_to_hex(nb_to_bytes(self.H)))
# print('IV: ' + bytes_to_hex(iv))
# print('Key: ' + bytes_to_hex(self.client_AES_key))
# print('Plaintext: ' + bytes_to_hex(plaintext))
# print('Ciphertext: ' + bytes_to_hex(ciphertext))
# print('Auth tag: ' + bytes_to_hex(auth_tag))
return iv[4:] + ciphertext + auth_tag
An attempt to translate this to C# code is below (sorry for the amateurish code, I am a newbie):
EDIT:
Created an array which got values from GetBytes, and printed the result:
byte[] incr = BitConverter.GetBytes((int) 2);
cf.printBuf(incr, (String) "Array:");
return;
Noticed that the result was "02 00 00 00". Hence I guess my machine is little endian
Made some changes to the code as rodrigogq mentioned. Below is the latest code. It is still not working:
Verified that GHASH, GF_mult and H_mult are giving same results. Below is the verification code:
Python:
key = "\xab\xcd\xab\xcd"
key = key * 10
h = "\x00\x00"
a = AES_GCM(key, 128, h)
H = 200
A = "\x02" * 95
C = "\x02" * 95
D = a.GHASH(H, A, C)
print(D)
C#:
BigInteger H = new BigInteger(200);
byte[] A = new byte[95];
byte[] C = new byte[95];
for (int i = 0; i < 95; i ++)
{
A[i] = 2;
C[i] = 2;
}
BigInteger a = e.GHASH(H, A, C);
Console.WriteLine(a);
Results:
For both: 129209628709014910494696220101529767594
EDIT: Now the outputs are agreeing between Python and C#. So essentially the porting is done :) However, these outputs still don't agree with Wireshark. Hence, the handshake is still failing. May be something wrong with the procedure or the contents. Below is the working code
EDIT: Finally managed to get the code working. Below is the code that resulted in a successful handshake
Working Code:
/*
* Receiving seqNum as UInt64 and content_type as byte
*
*/
public byte[] AES_Encrypt_GCM(byte[] client_write_key, byte[] client_write_iv, byte[] plaintext, UInt64 seqNum, byte content_type)
{
int plaintext_size = plaintext.Length;
List<byte> temp = new List<byte>();
byte[] init_bytes = new byte[16];
Array.Clear(init_bytes, 0, 16);
byte[] encrypted = AES_Encrypt_ECB(init_bytes, client_write_key, 128);
Array.Reverse(encrypted);
BigInteger H_client = new BigInteger(encrypted);
if (H_client < 0)
{
temp.Clear();
temp.TrimExcess();
temp.AddRange(H_client.ToByteArray());
temp.Add(0);
H_client = new BigInteger(temp.ToArray());
}
Random rnd = new Random();
byte[] random = new byte[8];
rnd.NextBytes(random);
/*
* incr is little endian, but it needs to be in big endian format
*
*/
byte[] incr = BitConverter.GetBytes((int) 2);
Array.Reverse(incr);
/*
* Counter = First 4 bytes of IV + 8 Random bytes + 4 bytes of sequential value (starting at 2)
*
*/
temp.Clear();
temp.TrimExcess();
temp.AddRange(client_write_iv);
temp.AddRange(random);
byte[] iv = temp.ToArray();
temp.AddRange(incr);
byte[] counter = temp.ToArray();
AES_CTR aesctr = new AES_CTR(counter);
ICryptoTransform ctrenc = aesctr.CreateEncryptor(client_write_key, null);
byte[] ctext = ctrenc.TransformFinalBlock(plaintext, 0, plaintext_size);
byte[] seq_num = BitConverter.GetBytes(seqNum);
/*
* Using UInt16 instead of short
*
*/
byte[] tls_version = BitConverter.GetBytes((UInt16) 771);
Console.WriteLine("Plain Text size = {0}", plaintext_size);
byte[] plaintext_size_array = BitConverter.GetBytes((UInt16) plaintext_size);
/*
* Size was returned as 10 00 instead of 00 10
*
*/
Array.Reverse(plaintext_size_array);
temp.Clear();
temp.TrimExcess();
temp.AddRange(seq_num);
temp.Add(content_type);
temp.AddRange(tls_version);
temp.AddRange(plaintext_size_array);
byte[] auth_data = temp.ToArray();
BigInteger auth_tag = GHASH(H_client, auth_data, ctext);
Console.WriteLine("H = {0}", H_client);
this.printBuf(plaintext, "plaintext = ");
this.printBuf(auth_data, "A = ");
this.printBuf(ctext, "C = ");
this.printBuf(client_write_key, "client_AES_key = ");
this.printBuf(iv.ToArray(), "iv = ");
Console.WriteLine("Auth Tag just after GHASH: {0}", auth_tag);
AesCryptoServiceProvider aes2 = new AesCryptoServiceProvider();
aes2.Key = client_write_key;
aes2.Mode = CipherMode.ECB;
aes2.Padding = PaddingMode.None;
aes2.KeySize = 128;
ICryptoTransform transform1 = aes2.CreateEncryptor();
byte[] cval = {0, 0, 0, 1};
temp.Clear();
temp.TrimExcess();
temp.AddRange(iv);
temp.AddRange(cval);
byte[] encrypted1 = AES_Encrypt_ECB(temp.ToArray(), client_write_key, 128);
Array.Reverse(encrypted1);
BigInteger nenc = new BigInteger(encrypted1);
if (nenc < 0)
{
temp.Clear();
temp.TrimExcess();
temp.AddRange(nenc.ToByteArray());
temp.Add(0);
nenc = new BigInteger(temp.ToArray());
}
this.printBuf(nenc.ToByteArray(), "NENC = ");
Console.WriteLine("NENC: {0}", nenc);
auth_tag ^= nenc;
byte[] auth_tag_array = auth_tag.ToByteArray();
Array.Reverse(auth_tag_array);
this.printBuf(auth_tag_array, "Final Auth Tag Byte Array: ");
Console.WriteLine("Final Auth Tag: {0}", auth_tag);
this.printBuf(random, "Random sent = ");
temp.Clear();
temp.TrimExcess();
temp.AddRange(random);
temp.AddRange(ctext);
temp.AddRange(auth_tag_array);
return temp.ToArray();
}
public void printBuf(byte[] data, String heading)
{
int numBytes = 0;
Console.Write(heading + "\"");
if (data == null)
{
return;
}
foreach (byte element in data)
{
Console.Write("\\x{0}", element.ToString("X2"));
numBytes = numBytes + 1;
if (numBytes == 32)
{
Console.Write("\r\n");
numBytes = 0;
}
}
Console.Write("\"\r\n");
}
public BigInteger GF_mult(BigInteger x, BigInteger y)
{
BigInteger product = new BigInteger(0);
BigInteger e10 = BigInteger.Parse("00E1000000000000000000000000000000", NumberStyles.AllowHexSpecifier);
/*
* Below operation y >> i fails if i is UInt32, so leaving it as int
*
*/
int i = 127;
while (i != -1)
{
product = product ^ (x * ((y >> i) & 1));
x = (x >> 1) ^ ((x & 1) * e10);
i = i - 1;
}
return product;
}
public BigInteger H_mult(BigInteger H, BigInteger val)
{
BigInteger product = new BigInteger(0);
int i = 0;
/*
* Below operation (val & 0xFF) << (8 * i) fails if i is UInt32, so leaving it as int
*
*/
while (i < 16)
{
product = product ^ GF_mult(H, (val & 0xFF) << (8 * i));
val = val >> 8;
i = i + 1;
}
return product;
}
public BigInteger GHASH(BigInteger H, byte[] A, byte[] C)
{
int C_len = C.Length;
List <byte> temp = new List<byte>();
int plen = 16 - (A.Length % 16);
byte[] zeroes = new byte[plen];
Array.Clear(zeroes, 0, zeroes.Length);
temp.AddRange(A);
temp.AddRange(zeroes);
temp.Reverse();
BigInteger A_padded = new BigInteger(temp.ToArray());
temp.Clear();
temp.TrimExcess();
byte[] C1;
if ((C_len % 16) != 0)
{
plen = 16 - (C_len % 16);
byte[] zeroes1 = new byte[plen];
Array.Clear(zeroes, 0, zeroes.Length);
temp.AddRange(C);
temp.AddRange(zeroes1);
C1 = temp.ToArray();
}
else
{
C1 = new byte[C.Length];
Array.Copy(C, 0, C1, 0, C.Length);
}
temp.Clear();
temp.TrimExcess();
BigInteger tag = new BigInteger();
tag = H_mult(H, A_padded);
this.printBuf(H.ToByteArray(), "H Byte Array:");
for (int i = 0; i < (int) (C1.Length / 16); i ++)
{
byte[] toTake;
if (i == 0)
{
toTake = C1.Take(16).ToArray();
}
else
{
toTake = C1.Skip(i * 16).Take(16).ToArray();
}
Array.Reverse(toTake);
BigInteger tempNum = new BigInteger(toTake);
tag ^= tempNum;
tag = H_mult(H, tag);
}
byte[] A_arr = BitConverter.GetBytes((long) (8 * A.Length));
/*
* Want length to be "00 00 00 00 00 00 00 xy" format
*
*/
Array.Reverse(A_arr);
byte[] C_arr = BitConverter.GetBytes((long) (8 * C_len));
/*
* Want length to be "00 00 00 00 00 00 00 xy" format
*
*/
Array.Reverse(C_arr);
temp.AddRange(A_arr);
temp.AddRange(C_arr);
temp.Reverse();
BigInteger array_int = new BigInteger(temp.ToArray());
tag = tag ^ array_int;
tag = H_mult(H, tag);
return tag;
}
Using SSL decryption in wireshark (using private key), I found that:
The nonce calculated by the C# code is same as that in wireshark (fixed part is client_write_IV and variable part is 8 bytes random)
The value of AAD (auth_data above) (client_write_key, seqNum + ctype + tls_version + plaintext_size) is matching with wireshark value
Cipher text (ctext above) (the C in GHASH(H, A, C)), is also matching the wireshark calculated value
However, the auth_tag calculation (GHASH(H_client, auth_data, ctext)) is failing. It would be great if someone could guide me as to what could be wrong in GHASH function. I just did a basic comparison of results of GF_mult function in python and C#, but the results are not matching too
This is not a final solution, but just an advice. I have seen you are using a lot the function BitConverter.GetBytes, int instead of Int32 or Int16.
The remarks from the official documentation says:
The order of bytes in the array returned by the GetBytes method
depends on whether the computer architecture is little-endian or
big-endian.
As for when you are using the BigInteger structure, it seems to be expecting always the little-endian order:
value
Type: System.Byte[]
An array of byte values in little-endian order.
Prefer using the Int32 and Int16 and pay attention to the order of the bytes before using it on these calculations.
Use log4net to log all the operations. Would be nice to put the same logs in the python program so that you could compare then at once, and check exactly where the calculations change.
Hope this give some tips on where to start.
I'm trying to use Rfc2898DeriveBytes for Android Java and .NET C# framework to generate key for encryption and decryption process.
The problem is in spite of that the input values are same, I'm getting different keys in .NET and Java.
.NET code:
private void btnRfc2898DeriveBytes_Click(object sender, EventArgs e)
{
byte[] salt = new byte[] { 19, 3, 24, 18, 14, 42, 57, 23 };
Rfc2898DeriveBytes keyGenerator = null;
keyGenerator = new Rfc2898DeriveBytes("somestring", salt, 1000);
txtRfc2898DeriveBytes.Text = System.Text.Encoding.UTF8.GetString(keyGenerator.GetBytes(16));
}
Java Code (used in an android application):
byte[] salt = new byte[] { 19, 3, 24, 18, 14, 42, 57, 23 };
Rfc2898DeriveBytes keyGenerator = null;
try {
keyGenerator = new Rfc2898DeriveBytes("somestring", salt, 1000);
} catch (InvalidKeyException e1) {
e1.printStackTrace();
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Log.i("key = ", decodeUTF8(keyGenerator.getBytes(16)));
Java Decode Method:
String decodeUTF8(byte[] bytes) {
private final Charset UTF8_CHARSET = Charset.forName("UTF-8");
return new String(bytes, UTF8_CHARSET);
}
Rfc2898DeriveBytes java class:
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* RFC 2898 password derivation compatible with .NET Rfc2898DeriveBytes class.
*/
public class Rfc2898DeriveBytes {
private Mac _hmacSha1;
private byte[] _salt;
private int _iterationCount;
private byte[] _buffer = new byte[20];
private int _bufferStartIndex = 0;
private int _bufferEndIndex = 0;
private int _block = 1;
/**
* Creates new instance.
* #param password The password used to derive the key.
* #param salt The key salt used to derive the key.
* #param iterations The number of iterations for the operation.
* #throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
* #throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
*/
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) throws NoSuchAlgorithmException, InvalidKeyException {
if ((salt == null) || (salt.length < 8)) { throw new InvalidKeyException("Salt must be 8 bytes or more."); }
if (password == null) { throw new InvalidKeyException("Password cannot be null."); }
this._salt = salt;
this._iterationCount = iterations;
this._hmacSha1 = Mac.getInstance("HmacSHA1");
this._hmacSha1.init(new SecretKeySpec(password, "HmacSHA1"));
}
/**
* Creates new instance.
* #param password The password used to derive the key.
* #param salt The key salt used to derive the key.
* #param iterations The number of iterations for the operation.
* #throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
* #throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
* #throws UnsupportedEncodingException UTF-8 encoding is not supported.
*/
public Rfc2898DeriveBytes(String password, byte[] salt, int iterations) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
this(password.getBytes("UTF8"), salt, iterations);
}
/**
* Creates new instance.
* #param password The password used to derive the key.
* #param salt The key salt used to derive the key.
* #throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
* #throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
* #throws UnsupportedEncodingException UTF-8 encoding is not supported.
*/
public Rfc2898DeriveBytes(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
this(password, salt, 0x3e8);
}
/**
* Returns a pseudo-random key from a password, salt and iteration count.
* #param count Number of bytes to return.
* #return Byte array.
*/
public byte[] getBytes(int count) {
byte[] result = new byte[count];
int resultOffset = 0;
int bufferCount = this._bufferEndIndex - this._bufferStartIndex;
if (bufferCount > 0) { //if there is some data in buffer
if (count < bufferCount) { //if there is enough data in buffer
System.arraycopy(this._buffer, this._bufferStartIndex, result, 0, count);
this._bufferStartIndex += count;
return result;
}
System.arraycopy(this._buffer, this._bufferStartIndex, result, 0, bufferCount);
this._bufferStartIndex = this._bufferEndIndex = 0;
resultOffset += bufferCount;
}
while (resultOffset < count) {
int needCount = count - resultOffset;
this._buffer = this.func();
if (needCount > 20) { //we one (or more) additional passes
System.arraycopy(this._buffer, 0, result, resultOffset, 20);
resultOffset += 20;
} else {
System.arraycopy(this._buffer, 0, result, resultOffset, needCount);
this._bufferStartIndex = needCount;
this._bufferEndIndex = 20;
return result;
}
}
return result;
}
private byte[] func() {
this._hmacSha1.update(this._salt, 0, this._salt.length);
byte[] tempHash = this._hmacSha1.doFinal(getBytesFromInt(this._block));
this._hmacSha1.reset();
byte[] finalHash = tempHash;
for (int i = 2; i <= this._iterationCount; i++) {
tempHash = this._hmacSha1.doFinal(tempHash);
for (int j = 0; j < 20; j++) {
finalHash[j] = (byte)(finalHash[j] ^ tempHash[j]);
}
}
if (this._block == 2147483647) {
this._block = -2147483648;
} else {
this._block += 1;
}
return finalHash;
}
private static byte[] getBytesFromInt(int i) {
return new byte[] { (byte)(i >>> 24), (byte)(i >>> 16), (byte)(i >>> 8), (byte)i };
}
}
Edit:
I also convert it to Hex, but getting different values also.
C# function:
public static String encode(byte[] data)
{
char[] lookup = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
int i = 0, p = 2, l = data.Length;
char[] c = new char[l * 2 + 2];
byte d; c[0] = '0'; c[1] = 'x';
while (i < l)
{
d = data[i++];
c[p++] = lookup[d / 0x10];
c[p++] = lookup[d % 0x10];
}
return new string(c, 0, c.Length);
}
Java method:
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
What I'm doing wrong :( ?
Here:
txtRfc2898DeriveBytes.Text = System.Text.Encoding.UTF8.GetString(
keyGenerator.GetBytes(16));
and here:
decodeUTF8(keyGenerator.getBytes(16)
you are using UTF-8 to decode data into a string that isn't UTF-8. Since that does not have a defined output, it is not unreasonable that the two undefined outputs can be different.
Rather than decoding as UTF-8: if you need it as a string, use base-16 (aka "hex") or base-64 to represent the generated bytes. A text encoding (such as UTF-8) is only well-defined when converting string data (in the range of characters supported by that encoding) to/from binary data in that encoding. It is incorrect to "decode" arbitrary binary via a text encoding.
I re-solved the issue, the following code generate same values for me in .NET and Java.
.NET code:
private void btnKey_Click(object sender, EventArgs e)
{
byte[] salt = new byte[] { 172, 137, 25, 56, 156, 100, 136, 211, 84, 67, 96, 10, 24, 111, 112, 137, 3 };
int iterations = 1024;
var rfc2898 = new System.Security.Cryptography.Rfc2898DeriveBytes("_sOme*ShaREd*SecreT", salt, iterations);
byte[] key = rfc2898.GetBytes(16);
String keyB64 = Convert.ToBase64String(key);
txtRfc2898DeriveBytes.Text = keyB64;
}
Java code:
String password = "_sOme*ShaREd*SecreT";
byte[] salta = new byte[]{-84, -119, 25, 56, -100, 100, -120, -45, 84, 67, 96, 10, 24, 111, 112, -119, 3};
SecretKeyFactory factory = null;
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
} catch (NoSuchAlgorithmException e2) {
e2.printStackTrace();
}
KeySpec spec = new PBEKeySpec(password.toCharArray(), salta, 1024, 128);
SecretKey tmp = null;
try {
tmp = factory.generateSecret(spec);
} catch (InvalidKeySpecException e2) {
e2.printStackTrace();
}
SecretKeySpec secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Log.i("The secret Key: " , Base64.encodeToString(secret.getEncoded(), 0 ));
Guys, I'm trying to implement a PBKDF2 function in C# that creates a WPA Shared key. I've found some here: http://msdn.microsoft.com/en-us/magazine/cc163913.aspx that seems to produce a valid result, but it's one byte too short... and the wrong PSK value.
To test the output, I am comparing it to this: http://www.xs4all.nl/~rjoris/wpapsk.html or http://anandam.name/pbkdf2/
I did find one way of getting this to work with a built in library to C# called Rfc2898DeriveBytes. Using this, I get a valid output using:
Rfc2898DeriveBytes k3 = new Rfc2898DeriveBytes(pwd1, salt1, 4096);
byte[] answers = k3.GetBytes(32);
Now, the one limitation I have using Rfc2898DeriveBytes is the "salt" must be 8 octets long. If it is shorter, the Rfc2898DeriveBytes throws an exception. I was thinking all I had to do was pad the salt (if it was shorter) to 8 bytes, and I'd be good. But NO! I've tried pretty much every combination of padding with a shorter salt, but I cannot duplicate the results I get from those two websites above.
So bottom line is, does this mean the Rfc2898DeriveBytes just simply won't work with a source salt shorter than 8 bytes? If so, does anyone know of any C# code I could use that implements PBKDF2 for WPA Preshared key?
Here is an implementation that does not require the 8 byte salt.
You can calculate a WPA key as follows:
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(passphrase, Encoding.UTF8.GetBytes(name), 4096);
key = rfc2898.GetBytes(32);
public class Rfc2898DeriveBytes : DeriveBytes
{
const int BlockSize = 20;
uint block;
byte[] buffer;
int endIndex;
readonly HMACSHA1 hmacsha1;
uint iterations;
byte[] salt;
int startIndex;
public Rfc2898DeriveBytes(string password, int saltSize)
: this(password, saltSize, 1000)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt)
: this(password, salt, 1000)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
{
if (saltSize < 0)
{
throw new ArgumentOutOfRangeException("saltSize");
}
byte[] data = new byte[saltSize];
new RNGCryptoServiceProvider().GetBytes(data);
Salt = data;
IterationCount = iterations;
hmacsha1 = new HMACSHA1(new UTF8Encoding(false).GetBytes(password));
Initialize();
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations)
{
}
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
{
Salt = salt;
IterationCount = iterations;
hmacsha1 = new HMACSHA1(password);
Initialize();
}
static byte[] Int(uint i)
{
byte[] bytes = BitConverter.GetBytes(i);
byte[] buffer2 = new byte[] {bytes[3], bytes[2], bytes[1], bytes[0]};
if (!BitConverter.IsLittleEndian)
{
return bytes;
}
return buffer2;
}
byte[] DeriveKey()
{
byte[] inputBuffer = Int(block);
hmacsha1.TransformBlock(salt, 0, salt.Length, salt, 0);
hmacsha1.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
byte[] hash = hmacsha1.Hash;
hmacsha1.Initialize();
byte[] buffer3 = hash;
for (int i = 2; i <= iterations; i++)
{
hash = hmacsha1.ComputeHash(hash);
for (int j = 0; j < BlockSize; j++)
{
buffer3[j] = (byte) (buffer3[j] ^ hash[j]);
}
}
block++;
return buffer3;
}
public override byte[] GetBytes(int bytesToGet)
{
if (bytesToGet <= 0)
{
throw new ArgumentOutOfRangeException("bytesToGet");
}
byte[] dst = new byte[bytesToGet];
int dstOffset = 0;
int count = endIndex - startIndex;
if (count > 0)
{
if (bytesToGet < count)
{
Buffer.BlockCopy(buffer, startIndex, dst, 0, bytesToGet);
startIndex += bytesToGet;
return dst;
}
Buffer.BlockCopy(buffer, startIndex, dst, 0, count);
startIndex = endIndex = 0;
dstOffset += count;
}
while (dstOffset < bytesToGet)
{
byte[] src = DeriveKey();
int num3 = bytesToGet - dstOffset;
if (num3 > BlockSize)
{
Buffer.BlockCopy(src, 0, dst, dstOffset, BlockSize);
dstOffset += BlockSize;
}
else
{
Buffer.BlockCopy(src, 0, dst, dstOffset, num3);
dstOffset += num3;
Buffer.BlockCopy(src, num3, buffer, startIndex, BlockSize - num3);
endIndex += BlockSize - num3;
return dst;
}
}
return dst;
}
void Initialize()
{
if (buffer != null)
{
Array.Clear(buffer, 0, buffer.Length);
}
buffer = new byte[BlockSize];
block = 1;
startIndex = endIndex = 0;
}
public override void Reset()
{
Initialize();
}
public int IterationCount
{
get
{
return (int) iterations;
}
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("value");
}
iterations = (uint) value;
Initialize();
}
}
public byte[] Salt
{
get
{
return (byte[]) salt.Clone();
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
salt = (byte[]) value.Clone();
Initialize();
}
}
}
I get matching results when comparing key-derivation from .NET's Rfc2898DeriveBytes and Anandam's PBKDF2 Javascript implementation.
I put together an example of packaging SlowAES and Anandam's PBKDF2 into Windows Script Components. Using this implementation shows good interop with the .NET RijndaelManaged class and the Rfc2898DeriveBytes class.
See also:
AES in Javascript
Getting SlowAES and RijndaelManaged to play together
All of these go further than what you are asking for. They all show interop of the AES encryption. But to get interop on encryption, it is a necessary pre-requisite to have interop (or matching outputs) on the password-based key derivation.
Looking at the Microsoft link, I made some changes in order to make the PMK the same as those discovered in the links you put forward.
Change the SHA algorithm from SHA256Managed to SHA1Managed for the inner and outer hash.
Change HASH_SIZE_IN_BYTES to equal 20 rather than 34.
This produces the correct WPA key.
I know it's a bit late coming, but I've only just started looking for this sort of informatin and thought I could help others out. If anyone does read this post, any ideas on the PRF function and how to do it within C#?
This expands on Dodgyrabbit's answer and his code helped to fix mine as I developed this. This generic class can use any HMAC-derived class in C#. This is .NET 4 because of the parameters with default values, but if those were changed then this should work down to .NET 2, but I haven't tested that. USE AT YOUR OWN RISK.
I have also posted this on my blog, The Albequerque Left Turn, today.
using System;
using System.Text;
using System.Security.Cryptography;
namespace System.Security.Cryptography
{
//Generic PBKDF2 Class that can use any HMAC algorithm derived from the
// System.Security.Cryptography.HMAC abstract class
// PER SPEC RFC2898 with help from user Dodgyrabbit on StackExchange
// http://stackoverflow.com/questions/1046599/pbkdf2-implementation-in-c-sharp-with-rfc2898derivebytes
// the use of default values for parameters in the functions puts this at .NET 4
// if you remove those defaults and create the required constructors, you should be able to drop to .NET 2
// USE AT YOUR OWN RISK! I HAVE TESTED THIS AGAINST PUBLIC TEST VECTORS, BUT YOU SHOULD
// HAVE YOUR CODE PEER-REVIEWED AND SHOULD FOLLOW BEST PRACTICES WHEN USING CRYPTO-ANYTHING!
// NO WARRANTY IMPLIED OR EXPRESSED, YOU ARE ON YOUR OWN!
// PUBLIC DOMAIN! NO COPYRIGHT INTENDED OR RESERVED!
//constrain T to be any class that derives from HMAC, and that exposes a new() constructor
public class PBKDF2<T>: DeriveBytes where T : HMAC, new()
{
//Internal variables and public properties
private int _blockSize = -1; // the byte width of the output of the HMAC algorithm
byte[] _P = null;
int _C = 0;
private T _hmac;
byte[] _S = null;
// if you called the initializer/constructor specifying a salt size,
// you will need this property to GET the salt after it was created from the crypto rng!
// GET THIS BEFORE CALLING GETBYTES()! OBJECT WILL BE RESET AFTER GETBYTES() AND
// SALT WILL BE LOST!!
public byte[] Salt { get { return (byte[])_S.Clone(); } }
// Constructors
public PBKDF2(string Password, byte[] Salt, int IterationCount = 1000)
{ Initialize(Password, Salt, IterationCount); }
public PBKDF2(byte[] Password, byte[] Salt, int IterationCount = 1000)
{ Initialize(Password, Salt, IterationCount); }
public PBKDF2(string Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{ Initialize(Password, SizeOfSaltInBytes, IterationCount);}
public PBKDF2(byte[] Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{ Initialize(Password, SizeOfSaltInBytes, IterationCount);}
//All Construtors call the corresponding Initialize methods
public void Initialize(string Password, byte[] Salt, int IterationCount = 1000)
{
if (string.IsNullOrWhiteSpace(Password))
throw new ArgumentException("Password must contain meaningful characters and not be null.", "Password");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
Initialize(new UTF8Encoding(false).GetBytes(Password), Salt, IterationCount);
}
public void Initialize(byte[] Password, byte[] Salt, int IterationCount = 1000)
{
//all Constructors/Initializers eventually lead to this one which does all the "important" work
if (Password == null || Password.Length == 0)
throw new ArgumentException("Password cannot be null or empty.", "Password");
if (Salt == null)
Salt = new byte[0];
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
_P = (byte[])Password.Clone();
_S = (byte[])Salt.Clone();
_C = IterationCount;
//determine _blockSize
_hmac = new T();
_hmac.Key = new byte[] { 0 };
byte[] test = _hmac.ComputeHash(new byte[] { 0 });
_blockSize = test.Length;
}
public void Initialize(string Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{
if (string.IsNullOrWhiteSpace(Password))
throw new ArgumentException("Password must contain meaningful characters and not be null.", "Password");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
Initialize(new UTF8Encoding(false).GetBytes(Password), SizeOfSaltInBytes, IterationCount);
}
public void Initialize(byte[] Password, int SizeOfSaltInBytes, int IterationCount = 1000)
{
if (Password == null || Password.Length == 0)
throw new ArgumentException("Password cannot be null or empty.", "Password");
if (SizeOfSaltInBytes < 0)
throw new ArgumentOutOfRangeException("SizeOfSaltInBytes");
if (IterationCount < 1)
throw new ArgumentOutOfRangeException("IterationCount");
// You didn't specify a salt, so I'm going to create one for you of the specific byte length
byte[] data = new byte[SizeOfSaltInBytes];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(data);
// and then finish initializing...
// Get the salt from the Salt parameter BEFORE calling GetBytes()!!!!!!!!!!!
Initialize(Password, data, IterationCount);
}
~PBKDF2()
{
//*DOOT* clean up in aisle 5! *KEKERKCRACKLE*
this.Reset();
}
// required by the Derive Bytes class/interface
// this is where you request your output bytes after Initialize
// state of class Reset after use!
public override byte[] GetBytes(int ByteCount)
{
if (_S == null || _P == null)
throw new InvalidOperationException("Object not Initialized!");
if (ByteCount < 1)// || ByteCount > uint.MaxValue * blockSize)
throw new ArgumentOutOfRangeException("ByteCount");
int totalBlocks = (int)Math.Ceiling((decimal)ByteCount / _blockSize);
int partialBlock = (int)(ByteCount % _blockSize);
byte[] result = new byte[ByteCount];
byte[] buffer = null;
// I'm using TT here instead of T from the spec because I don't want to confuse it with
// the generic object T
for (int TT = 1; TT <= totalBlocks; TT++)
{
// run the F function with the _C number of iterations for block number TT
buffer = _F((uint)TT);
//IF we're not at the last block requested
//OR the last block requested is whole (not partial)
// then take everything from the result of F for this block number TT
//ELSE only take the needed bytes from F
if (TT != totalBlocks || (TT == totalBlocks && partialBlock == 0))
Buffer.BlockCopy(buffer, 0, result, _blockSize * (TT - 1), _blockSize);
else
Buffer.BlockCopy(buffer, 0, result, _blockSize * (TT - 1), partialBlock);
}
this.Reset(); // force cleanup after every use! Cannot be reused!
return result;
}
// required by the Derive Bytes class/interface
public override void Reset()
{
_C = 0;
_P.Initialize(); // the compiler might optimize this line out! :(
_P = null;
_S.Initialize(); // the compiler might optimize this line out! :(
_S = null;
if (_hmac != null)
_hmac.Clear();
_blockSize = -1;
}
// the core function of the PBKDF which does all the iterations
// per the spec section 5.2 step 3
private byte[] _F(uint I)
{
//NOTE: SPEC IS MISLEADING!!!
//THE HMAC FUNCTIONS ARE KEYED BY THE PASSWORD! NEVER THE SALT!
byte[] bufferU = null;
byte[] bufferOut = null;
byte[] _int = PBKDF2<T>.IntToBytes(I);
_hmac = new T();
_hmac.Key = (_P); // KEY BY THE PASSWORD!
_hmac.TransformBlock(_S, 0, _S.Length, _S, 0);
_hmac.TransformFinalBlock(_int, 0, _int.Length);
bufferU = _hmac.Hash;
bufferOut = (byte[])bufferU.Clone();
for (int c = 1; c < _C; c++)
{
_hmac.Initialize();
_hmac.Key = _P; // KEY BY THE PASSWORD!
bufferU = _hmac.ComputeHash(bufferU);
_Xor(ref bufferOut, bufferU);
}
return bufferOut;
}
// XOR one array of bytes into another (which is passed by reference)
// this is the equiv of data ^= newData;
private void _Xor(ref byte[] data, byte[] newData)
{
for (int i = data.GetLowerBound(0); i <= data.GetUpperBound(0); i++)
data[i] ^= newData[i];
}
// convert an unsigned int into an array of bytes BIG ENDIEN
// per the spec section 5.2 step 3
static internal byte[] IntToBytes(uint i)
{
byte[] bytes = BitConverter.GetBytes(i);
if (!BitConverter.IsLittleEndian)
{
return bytes;
}
else
{
Array.Reverse(bytes);
return bytes;
}
}
}
}