private static byte[] CryptDeriveKey(byte[] key, int size, int
algorithmType) throws Exception {
byte[] buffer1 = new byte[64];
byte[] buffer2 = new byte[64];
Arrays.fill(buffer1, (byte)0x36);
Arrays.fill(buffer2, (byte)0x5C);
for (int i = 0; i < key.length; i++) {
buffer1[ i ] ^= key[ i ];
buffer2[ i ] ^= key[ i ];
}
try {
buffer1 = calcHash(buffer1, algorithmType).getBytes();
buffer2 = calcHash(buffer2, algorithmType).getBytes();
} catch (NoSuchAlgorithmException e) {
throw new Exception(e);
}
byte[] derivedKey = new byte[size];
for (int i = 0; i < size; i++) {
if(i < buffer1.length)
derivedKey[ i ] = buffer1[ i ];
else
derivedKey[ i ] = buffer2[ i - buffer1.length ];
}
return derivedKey;
}
Any ideas?
If possible, can you please add enough code so that I can execute your algo with
the Java2 SDK? For example, show all necessary Import statements. Please post
your test vectors, then I can use my pyHmac.py to compare.
>
> private static byte[] CryptDeriveKey(byte[] key, int size, int
> algorithmType) throws Exception {
> byte[] buffer1 = new byte[64];
> byte[] buffer2 = new byte[64];
>
> Arrays.fill(buffer1, (byte)0x36);
> Arrays.fill(buffer2, (byte)0x5C);
>
> for (int i = 0; i < key.length; i++) {
> buffer1[ i ] ^= key[ i ];
> buffer2[ i ] ^= key[ i ];
> }
The above step is more like this:
hsh1 = calcHash(key, algorithmType).getBytes();
for (int i = 0; i < hsh1.length; i++) {
buffer1[ i ] ^= hsh1[ i ];
buffer2[ i ] ^= hsh1[ i ];
}
>
> try {
> buffer1 = calcHash(buffer1, algorithmType).getBytes();
> buffer2 = calcHash(buffer2, algorithmType).getBytes();
> } catch (NoSuchAlgorithmException e) {
> throw new Exception(e);
> }
You will need to concantenate the two hashes now, so use strings
try {
ihsh = calcHash(buffer1, algorithmType).toString(); //inner
ohsh = calcHash(buffer2, algorithmType).toString(); //outer
} catch (NoSuchAlgorithmException e) {
throw new Exception(e);
}
>
> byte[] derivedKey = new byte[size];
byte[] derivedKey = new byte[2*size];
forget below and do
derivedKey = (ihsh.hexdigest() + ohash.hexdigest() ).getBytes()
disclaimer: feel free to correct my Java
> for (int i = 0; i < size; i++) {
> if(i < buffer1.length)
> derivedKey[ i ] = buffer1[ i ];
> else
> derivedKey[ i ] = buffer2[ i - buffer1.length ];
> }
>
> return derivedKey;
> }
>
> Any ideas?
hth,
tlviewer
--
Password = test password
Data = The book is on the table
On C Program:
Password hash = 2C EB 02 A8 5F 6D 4D E6 C2 8B 2E 59 FD A8 86 D5 26 DA FB 0D
Session key hash = 12 93 D7 12 C2 CD D2 79 1D 43 2F CE 58 C4 33 CA 31 41 AA 77
On Java program:
Password hash = 2C EB 02 A8 5F 6D 4D E6 C2 8B 2E 59 FD A8 86 D5 26 DA FB 0D
Session key hash = 42 85 CA D4 84 9B 13 34 74 B5 EF 19 04 73 D6 6F 0C EF EA 99
The C code is:
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
#include <wincrypt.h>
#define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
void crypt();
void MyHandleError(char *s);
int main() {
crypt();
return 0;
}
void crypt() {
HCRYPTPROV hCryptProv;
HCRYPTKEY hKey;
HCRYPTHASH hHash;
HCRYPTHASH hHashSession;
CHAR szPassword[14] = "test password";
CHAR szData[25] = "The book is on the table";
DWORD dwPassLen = strlen(szPassword);
DWORD dwDataLen = strlen(szData);
DWORD dwSessionHashLen;
DWORD dwPassHashLen;
//--------------------------------------------------------------------
// Acquire a cryptographic provider context handle.
if(!CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) {
MyHandleError("Error during CryptAcquireContext!");
}
//--------------------------------------------------------------------
// Create an empty hash object.
if(!CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash)) {
MyHandleError("Error during CryptCreateHash!");
}
//--------------------------------------------------------------------
// Hash the password string.
if(!CryptHashData(hHash, (BYTE *)szPassword, dwPassLen, 0)) {
MyHandleError("Error during CryptHashData!");
}
if(!CryptGetHashParam(hHash, HP_HASHVAL, NULL, &dwPassHashLen, 0)) {
MyHandleError("Error during CryptGetHashParam!");
}
BYTE * szPassHash = new BYTE[dwPassHashLen + 1];
memset(szPassHash, 0, dwPassHashLen + 1);
if(!CryptGetHashParam(hHash, HP_HASHVAL, szPassHash, &dwPassHashLen, 0)) {
MyHandleError("Error during CryptGetHashParam!");
}
//--------------------------------------------------------------------
// Create a session key based on the hash of the password.
if(!CryptDeriveKey(hCryptProv, CALG_3DES, hHash, CRYPT_EXPORTABLE, &hKey)) {
MyHandleError("Error during CryptDeriveKey!");
}
//--------------------------------------------------------------------
// Create an empty hash object for session key
if(!CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHashSession)) {
MyHandleError("Error during CryptCreateHash for session key!");
}
//--------------------------------------------------------------------
// Create a hash of the session key
if(!CryptHashSessionKey(hHashSession, hKey, 0)) {
MyHandleError("Error during CryptHashSessionKey for session key!");
}
DWORD dwCount = sizeof(DWORD);
if(!CryptGetHashParam(hHashSession, HP_HASHSIZE, (BYTE
*)&dwSessionHashLen, &dwCount, 0)) {
MyHandleError("Error during CryptGetHashParam for session key - Getting
size!");
}
BYTE * szSessionHash = new BYTE[dwSessionHashLen + 1];
memset(szSessionHash, 0, dwSessionHashLen + 1);
if(!CryptGetHashParam(hHashSession, HP_HASHVAL, szSessionHash,
&dwSessionHashLen, 0)) {
MyHandleError("Error during CryptGetHashParam!");
}
// Destroy the session hash object.
if(hHashSession) {
if(!(CryptDestroyHash(hHashSession)))
MyHandleError("Error during session CryptDestroyHash");
}
// Destroy the hash object.
if(hHash) {
if(!(CryptDestroyHash(hHash)))
MyHandleError("Error during CryptDestroyHash");
}
// Destroy the session key.
if(hKey) {
if(!(CryptDestroyKey(hKey)))
MyHandleError("Error during CryptDestroyKey");
}
// Release the provider handle.
if(hCryptProv) {
if(!(CryptReleaseContext(hCryptProv, 0)))
MyHandleError("Error during CryptReleaseContext");
}
printf("The program to derive a key completed without error. \n");
}
//--------------------------------------------------------------------
// This example uses the function MyHandleError, a simple error
// handling function to print an error message and exit
// the program.
// For most applications, replace this function with one
// that does more extensive error reporting.
void MyHandleError(char *s) {
printf("An error occurred in running the program.\n");
printf("%s\n",s);
printf("Error number %x\n.",GetLastError());
printf("Program terminating.\n");
exit(1);
}
The Java code is:
package br.com.bradseg.cripto;
import java.io.File;
import java.io.FileWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
public class Crypto {
public static final int HASH_TYPE_MD5 = 1;
public static final int HASH_TYPE_SHA1 = 2;
public static byte[] calcHash(byte[] data, int algorithmType) throws
NoSuchAlgorithmException {
MessageDigest md = null;
if (HASH_TYPE_MD5 == algorithmType)
md = MessageDigest.getInstance("MD5");
else if (HASH_TYPE_SHA1 == algorithmType)
md = MessageDigest.getInstance("SHA-1");
else
throw new NoSuchAlgorithmException();
md.update(data);
byte bufferSaida[] = md.digest();
return bufferSaida;
}
public static String crypt(String data, String key, int algorithmType)
throws Exception {
byte[] hashKey = null;
Cipher cipher = null;
int keySize = 24; // 3DES key size
hashKey = calcHash(key.getBytes(), HASH_TYPE_SHA1);
byte[] derivedKey = cryptDeriveKey(hashKey, keySize, HASH_TYPE_SHA1);
byte[] sessionKey = calcHash(derivedKey, HASH_TYPE_SHA1);
/*SecretKeySpec secretKeySpec = new SecretKeySpec(derivedKey, "DESede");
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);*/
return "";
}
private static byte[] cryptDeriveKey(byte[] key, int size, int
algorithmType) throws Exception {
byte[] buffer1 = new byte[64];
byte[] buffer2 = new byte[64];
// Preenche os buffers
Arrays.fill(buffer1, (byte)0x36);
Arrays.fill(buffer2, (byte)0x5C);
// Faz o XOR da chave com os buffers
for (int i = 0; i < key.length; i++) {
buffer1[i] ^= key[i];
buffer2[i] ^= key[i];
}
// Calcula o hash utilizando o mesmo algoritmo utilizado na chave passada
try {
buffer1 = calcHash(buffer1, algorithmType);
buffer2 = calcHash(buffer2, algorithmType);
} catch (NoSuchAlgorithmException e) {
throw new Exception(e);
}
// Transporta os bytes dos buffers para a chave final até o tamanho desejado
byte[] derivedKey = new byte[size];
Your Java results are right. The C _session_key_hash_ is wrong.
Why don't you show _all_ the code you used to print the above hashes? Your
error might be a simple format problem.
Here's the Java code that I used to generate the above hashes
http://67.49.101.245/crypto/Crypto.java
I'm using Sun j2sdk1.4.2_02 on Win2k sp4, IE6 sp1.
hth,
tlviewer
I added the code to encrypt data in my test program.
>>>> Java >>>>
public static byte[] crypt(String data, String key, int algorithmType)
throws Exception {
byte[] hashKey = null;
Cipher cipher = null;
int keySize = 24; // 3DES key size
hashKey = calcHash(key.getBytes(), HASH_TYPE_SHA1);
byte[] derivedKey = cryptDeriveKey(hashKey, keySize, HASH_TYPE_SHA1);
byte[] sessionKey = calcHash(hashKey, HASH_TYPE_SHA1);
SecretKeySpec secretKeySpec = new SecretKeySpec(derivedKey, "DESede");
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] cryptData = cipher.doFinal(data.getBytes());
return cryptData;
}
>>>> C >>>>
The code below is after the CryptDeriveKey function.
DWORD dwDataOutLen = dwDataInLen;
if(!CryptEncrypt(hKey, 0, TRUE, 0, NULL, &dwDataOutLen, dwDataOutLen)) {
MyHandleError("Error during CryptEncrypt!");
}
BYTE * szDataOut = new BYTE[dwDataOutLen + 1];
memset(szDataOut, 0, dwDataOutLen + 1);
if(!CryptEncrypt(hKey, 0, TRUE, 0, szDataOut, &dwDataInLen, dwDataOutLen)) {
MyHandleError("Error during CryptEncrypt!");
}
I see. It seems that you have accounted for the CryptoAPI default
IV vector of 0x00 (8 bytes) in your Java, right?
The other difference is in odd parity enforcement. I forgot
about this myself, yesterday. When CryptoAPI is hashing the session
key, it is enforcing odd parity on the key before the hash.
I am trying to add this to the Java with an array lookup. I think
all will interop once you add this correction.
regards,
tlviewer
crypt("The book is on the table")=
50163bd5d902d3b8c254060f4418da4558d47cb6ab92ec31753a8818d48d312e
this is the result in CryptoAPI and Java when using zeroed iv vector.
regards,
tlviewer
The encrypted data that I got in C is:
AA FC BE 15 4F 56 30 9F 7A C2 70 A9 D6 6E 6B EF 62 36 56 80 B5 8F 92 C7 90
A5 92 F7 52 4D 52 8D
Did you change anything in C program?
I implemented in Java a method for adding the parity bit on a byte array but
the encrypted data in C still diferent from the one in Java.
The complete Java code is:
package foo.bar.cripto;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Crypto {
public static final int HASH_TYPE_MD5 = 1;
public static final int HASH_TYPE_SHA1 = 2;
public byte[] calcHash(byte[] data, int algorithmType) throws
NoSuchAlgorithmException {
MessageDigest md = null;
if (HASH_TYPE_MD5 == algorithmType)
md = MessageDigest.getInstance("MD5");
else if (HASH_TYPE_SHA1 == algorithmType)
md = MessageDigest.getInstance("SHA-1");
else
throw new NoSuchAlgorithmException();
md.update(data);
byte bufferSaida[] = md.digest();
return bufferSaida;
}
public byte[] crypt(String data, String key, int algorithmType) throws
Exception {
byte[] hashKey = null;
Cipher cipher = null;
int keySize = 21; // 3DES key size
hashKey = calcHash(key.getBytes(), HASH_TYPE_SHA1);
byte[] derivedKey = addParityBit(cryptDeriveKey(hashKey, keySize,
HASH_TYPE_SHA1));
byte[] iv = new byte[8];
Arrays.fill(iv, (byte)0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(derivedKey, "DESede");
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] cryptData = cipher.doFinal(data.getBytes());
return cryptData;
}
private byte[] cryptDeriveKey(byte[] key, int size, int algorithmType)
throws Exception {
byte[] buffer1 = new byte[64];
byte[] buffer2 = new byte[64];
// Preenche os buffers
Arrays.fill(buffer1, (byte) 0x36);
Arrays.fill(buffer2, (byte) 0x5C);
// Faz o XOR da chave com os buffers
for (int i = 0; i < key.length; i++) {
buffer1[i] ^= key[i];
buffer2[i] ^= key[i];
}
// Calcula o hash utilizando o mesmo algoritmo utilizado na chave passada
try {
buffer1 = calcHash(buffer1, algorithmType);
buffer2 = calcHash(buffer2, algorithmType);
} catch (NoSuchAlgorithmException e) {
throw new Exception(e);
}
// Transporta os bytes dos buffers para a chave final até o tamanho desejado
byte[] derivedKey = new byte[size];
for (int i = 0; i < size; i++) {
if (i < buffer1.length)
derivedKey[i] = buffer1[i];
else
derivedKey[i] = buffer2[i - buffer1.length];
}
return derivedKey;
}
private byte[] addParityBit(byte[] key) throws Exception {
if (key.length % 7 != 0)
throw new Exception("Invalid data length");
byte[] keyData = new byte[key.length + key.length / 7];
byte b, deslocamento = 0, sobra = 0, j = 0;
for (short i = 0; i < key.length; i++) {
b = (byte) ((sobra << 8 - deslocamento) | ((key[i] & 0xFF) >>
deslocamento));
sobra = (byte) (key[i] & (0x0FF >> (7 - deslocamento++)));
keyData[i + j] = addParityBit(b);
if ((i + 1) % 7 == 0) {
keyData[i + ++j] = addParityBit((byte) (sobra << 1));
sobra = 0;
deslocamento = 0;
}
}
return keyData;
}
private byte addParityBit(byte b) {
byte paridade = (byte) ((b >> 1) ^ (b >> 2) ^ (b >> 3) ^ (b >> 4) ^ (b >>
5) ^ (b >> 6) ^ (b >> 7));
return (byte) ((b & 0xFE) | (paridade & 0x01));
}
private void showBinary(byte value) {
StringBuffer sb = new StringBuffer();
for (int i = 7; i >= 0; i--)
sb.append((value >> i) & 0x01);
System.out.println(sb.toString());
}
}
You don't want to _add_ a parity bit. I see that you are taking 21 bytes
from CryptDeriveKey and adding the parity. No, you want to take 24
bytes from CDK() and change the least significant bit of each byte
to enforce odd parity.
Further, forcing odd parity should not change the ciphertext. However, it
will change the hash of the output from CDK(). When you pass a 24 byte
key to a 3des encryptor it is throwing away the LSB of every byte and only
using a net 168 bits.
I stand by my value. I get agreement in Java and CryptoAPI.
Can you please show your Java main().
crypt("The book is on the table")=
50163bd5d902d3b8c254060f4418da4558d47cb6ab92ec31753a8818d48d312e
regards,
tlviewer
private byte[] addParityBit(byte[] key) throws Exception {
byte[] keyData = new byte[key.length];
for (short i = 0; i < key.length; i++) {
byte b = key[i];
byte parity = (byte) ((b >> 1) ^ (b >> 2) ^ (b >> 3) ^ (b >> 4) ^ (b >>
5) ^ (b >> 6) ^ (b >> 7));
keyData[i] = (byte) ((b & 0xFE) | (parity & 0x01));
}
return keyData;
}