Conversão de rotina de criptografia em C para Java

Amigos

Estou convertendo a rotina abaixo (em C usando CriptoAPI) para JAVA mas empaquei na função CryptDeriveKey.

Alguém sabe como converter o código para JAVA?

bOk = CryptCreateHash(m_hProv, CALG_SHA1, 0, 0, &hHash); 
...
bOk = CryptHashData(hHash, vlKey, szKey, 0);
...
bOk = CryptDeriveKey(m_hProv, CALG_3DES, hHash, CRYPT_EXPORTABLE, &hKey);
...
bOk = CryptEncrypt(hKey, 0, TRUE, 0, OutBuffer, &szDataIn, szOutBuf);
...

[quote=LeoNicolas]Amigos

Estou convertendo a rotina abaixo (em C usando CriptoAPI) para JAVA mas empaquei na função CryptDeriveKey.

Alguém sabe como converter o código para JAVA?

bOk = CryptCreateHash(m_hProv, CALG_SHA1, 0, 0, &hHash); ... bOk = CryptHashData(hHash, vlKey, szKey, 0); ... bOk = CryptDeriveKey(m_hProv, CALG_3DES, hHash, CRYPT_EXPORTABLE, &hKey); ... bOk = CryptEncrypt(hKey, 0, TRUE, 0, OutBuffer, &szDataIn, szOutBuf); ... [/quote]

Pois é, preciso ver exatamente qual é o algoritmo usado em CryptDeriveKey. No JCE não existe um equivalente exatamente igual, é necessário ou implementá-lo, ou então achar algo pronto.
A documentação da Microsoft não é muito clara a respeito de CryptDeriveKey.

EDIT - Veja este post, e a resposta de edsonw (eu mesmo).

http://forum.java.sun.com/thread.jspa?threadID=516501&messageID=2486343

thingol,

eu pesquisei bastante na Internet e não achei nada muito consistente.
A documentação da Microsoft não deixa claro o que a API faz.

Mas, acho eu, que isso é um problema comum.
Alguém possivelmente deve ter algo implementado.

EDIT - Veja este post, e a última resposta de edsonw (eu mesmo).

http://forum.java.sun.com/thread.jspa?threadID=516501&messageID=2486343

thingol

Já tinha visto esse post mas achei o processo meio confuso.
Não sei se é possível, a partir dele, escrever a rotina em JAVA.

Vou dar então a receita de bolo, mas espere um pouquinho, que para explicar leva um certo tempo.

Aqui vai minha interpretação do texto do sr. Carlos Lopez.

a) Como você está querendo uma chave de 192 bits (Triple-DES), e você tem um hash SHA-1 (160 bits), estão faltando 32 bits. Para consegui-los:

The derivekey function is done by copying the appropriate number of bytes,
depending on the symmetric algorithm, from the resulting hash value. This
should work fine except for key lengths which are longer than resulting
hash value. In this case we do something a bit different. Take the
example where a 196 bit key is to be generated from a 128 bit hash value.
Using this one hash value we generate two additional hash values which we
concatenate and now use the appropriate number of bytes from this stream
for the symmetric key value. The two hash values are generated by
separately hashing one of two 64 byte buffers. The two buffers are
repeated bytes, for the first hash use the byte value 0x36 and for the
second use the byte value 0x5C, with the orignal hash value XORed with the
first 16 bytes of each buffer.

Vou representar o HASH SHA-1 dos dados originais por
HHHHHHHHHHHHHHHHHHHH
onde cada H representa um byte.

O primeiro buffer seria algo como
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
e o segundo buffer seria algo como
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

onde um < representa o byte 0x36 e um \ representa o byte 0x5C.

Agora vamos fazer o XOR do hash original com os 16 primeiros bytes de cada buffer.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
XOR
HHHHHHHHHHHHHHHH

****************<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

A mesma coisa para o segundo buffer.
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
XOR
HHHHHHHHHHHHHHHH

################\\\\\\\\\\\\\\\\\\\\\\\\

Agora podemos calcular H1 = Hash(****************<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<)
e H2 =
Hash(################\\\\\\\\\\\\\\\\\\\\\\\\)

Como você só quer mais 32 bits (8 bytes), dá para pegar os primeiros 8 bytes de H1.

É meio esquisito porque isso parece a definição do HMAC, mas não é
(do HMAC foram aproveitados os valores 0x36 e 0x5C).

Não sei se isso realmente faz o serviço de CryptDeriveKey (que falta faz
o “open-source” aqui…), mas é o caso de testar.

Olá

O que vou dizer a seguir pode ser bobagem porque faz tempo que não mexo com criptografia.

Acho que não é muito bom traduzir funções de criptografia. A meu ver o caminho deveria passar pelo entendimento de para que a função em C está sendo usada e então desenvolver algo em Java que a substitua.

Sei que as vezes a gente precisa fazer que o Java converse com C na interpretação de mensagens criptografadas. Minha experiência é que o ideal nestes casos é que ambos os sistemas sejam desenvolvidos de comum acordo. Caso isto não seja possível por se tratar de um sistema legado então é aquela hora de respirar fundo, tomar um cafezinho, fazer aquela cara de inteligente e soltar a famosa frase: agora fudeu!

Mas deve ter solução como o thingol já mostrou.

Andei googlando por seu problema e descobri alguns links que com certeza você já viu. Mas vou coloca-los aqui para que outros possam saber do assunto:

MSDN - CryptDeriveKey

Generating a Key from a Password

Supporting CryptoApi in Real-World Applications

Encrypting and decrypting sensitive data using CryptoAPI

Symmetric Encryption: .NET, CryptoAPI and Java 2

Comparison of The Open Group’s GCS-API and Microsoft CryptoAPI Version 1.0

the old and the new? WINCRYPT.H

OpenNETCF.org-Smart Device Framework -PasswordDeriveBytes.CryptDeriveKey Method

[]s
Luca (apenas curioso)

De fato, andei procurando um pouco mais, e acho que o Carlos Lopez não explicou direito mesmo.
Em outro lugar foi sugerido que a Microsoft usou PBKDF1 ( veja em PKCS#9 v 2.0), ou RFC 2898.
Na verdade não sei mesmo como é que a Microsoft deriva as chaves.
Acho que é o caso de perguntar de novo em news://microsoft.public.platformsdk.security (também acessível via http://msdn.microsoft.com/newsgroups )

Mais uma vez agradeço a ajuda mas ainda acho que não está claro o processo.
Por exemplo, se temos dois buffers (H1 e H2), porque utilizamos apenas o H1? Então não existe a necessidade de calcular o H2?

Apos fazer o XOR da chave com o buffer, devemos fazer um hash do resultado? Se sim, que algortimo vamos usar? E ainda temos outro problema. Se a chave final deve ter 192 bits, quando fizermos o hash, se for utilizado SHA, o resultado terá 160 bits e, se usarmos MD5, terá 128 bits.

Como disse thingol, esse algoritmo é meio esquisito.

Alguém sabe como obter a chave (Session Key) gerada pela função CryptDeriveKey?
Ela fica armazenada dentro do provider e obtê-la vai ajudar na verificação com a chave gerada pelo algoritmo JAVA.

Olá

O link Symmetric Encryption: .NET, CryptoAPI and Java 2 não explica sobre como obtem a chave? Lá tem um link para How To Export/Import Plain Text Session Key Using CryptoAPI que talvez seja útil.

Veja também HOWTO: Obtain the plain text session key using CryptoAPI

[]s
Luca

Luca

Eu tentei usar a api CryptExportKey mas a SessionKey exportada é criptografada.

Olá

Mas o segundo link tem até código para download. Entendi que mostrava em plain text mas não entendi como colocar este teste para funcionar. Estou bem mal em coisas da turma da M$/VisualStudio/etc.

[]s
Luca

Andei olhando com calma a documentação da Microsoft sobre a api CryptDeriveKey (MSDN - CryptDeriveKey) e o documento diz:

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 required key length is longer than the hash value, the key is derived as follows:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. Concatenate the result of step 3 with the result of step 4.

  6. Use the first n bytes of the result of step 5 as the derived key.

Fiz a rotina e gerei a chave derivada.

Agora, como faço para utilizá-la no Cipher?

Eu tentei com “secretKey = new SecretKeySpec(newKey, “DES”);” mas o resultado, após a criptografia, ficou diferente do gerado pelo C.

Tb estou vendo, no forum da Microsoft, como faço para visualizar a Session Key gerada pela api CryptDeriveKey

Puxa, é que eu estava usando uma versão velha do MSDN (que deixo copiado na máquina, uma vez que quando vou fazer algo em C++ uso demais…).
Não sabia que a documentação de CryptDeriveKey explicava como se obter a chave.
Bom, você está quase lá. Para a chave do TripleDES, você precisa duas coisas:

  • Ver qual é o modo que está sendo usado pelo código original (ECB? CBC? CFB-8? etc.) e se você precisa de um vetor de inicialização (todos precisam, exceto o modo ECB).
    Não use simplesmente Cipher cp = Cipher.getInstance(“DESede”), porque vai usar algum default (veja na documentação - nem sei qual é o default porque não confio em defaults), passe o modo e o padding correto (por exemplo, “DESede/ECB/NoPadding” ou “DESede/CBC/PKCS5Padding”.)

  • Para SecretKeySpec, passar 24 bytes, e usar “DESede” como nome do algoritmo (não é TripleDES nem 3DES).

Mas que coisa chata… eheheh… não funciona.

Os dados criptografados pela rotina em C ficaram assim:

78 3D 3F E2 41 43 7A 73  40 E5 25 89 4B 4E 6F 07
B2 FE B6 08 2D 60 8D D4  98 A0 13 65 BD 1B 4B A5
41 52 6B 57 55 AE 12 FF  C2 B4 1E 14 7C 91 20 D2

A senha utilizada foi “Chave para criptografia”
Os dados são “Texto para teste de criptografia em java.”
Todos os dois sem aspas.

A classe (que ainda não está pronta) é a seguinte:

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.spec.SecretKeySpec;

public class Crypto {

	public static final int HASH_TYPE_MD5 = 1;
	public static final int HASH_TYPE_SHA = 2;

	public static String calcularHash(byte[] data, int algorithmType) throws NoSuchAlgorithmException {
		
		MessageDigest md = null;
		if (HASH_TYPE_MD5 == algorithmType)
			md = MessageDigest.getInstance("MD5");
		else if (HASH_TYPE_SHA == algorithmType)
			md = MessageDigest.getInstance("SHA");
		else
			throw new NoSuchAlgorithmException();

		md.update(data);
		byte bufferSaida[] = md.digest();

		return new String(bufferSaida);
	}

	public static String calcularHash(String data, int algorithmType) throws NoSuchAlgorithmException {
		return calcularHash(data.getBytes(), algorithmType);
	}

	public static String criptografar(String data, String key, int algorithmType) throws Exception {

		byte[] hashKey = calcularHash(key, HASH_TYPE_SHA).getBytes();
		
		SecretKeySpec secretKeySpec = null;
		Cipher cipher = null;
		int keySize = 24;
		
		byte[] derivedKey = CryptDeriveKey(hashKey, keySize, HASH_TYPE_SHA);

		// TODO: Remover Syso
		System.out.println(">>>>>>>>>>>>>>>>>>>>>>> Tentando com chave de tamanho = " + keySize + " bytes");
		secretKeySpec = new SecretKeySpec(derivedKey, "DESede");
		cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
		cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

		// TODO: Código de teste
		FileWriter fw = null;
		fw = new FileWriter(new File("c:\\dados\\crypto_3DES_Deriv_" + keySize + "_java.bin"));
		fw.write(new String(cipher.doFinal(data.getBytes())));
		fw.close();

		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 = calcularHash(buffer1, algorithmType).getBytes();
			buffer2 = calcularHash(buffer2, algorithmType).getBytes();
		} 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;
	}
}

Alguém tem alguma idéia?

Tentei obter a senha gerada (Session key) pela api CryptDeriveKey mas pelo que vi, isso não é possível. Apenas consegui onter o hash dessa chave.

Comparei com o hash da chave gerada pelo meu método CryptDeriveKey sendo o resultado diferente.

Segue abaixo o hash da session key obtida no C++

OBS.: O algoritmo usado foi SHA-1

A4 31 77 24 0F 2D 5D 64 F0 C5 19 F8 FB A7 E5 B4 7B 23 71 E4

Só uma pergunta. A conversão tem que ser identica ou bastaria ter as mesma propriedades criptográficas? Se for o segundo fica bem mais facil…

Olá

Também acho! Foi o que citei no meu primeiro post desta pasta.

[]s
Luca

O que necessito é poder, no java, descriptografar dados que foram gerados pela rotina em C. Por isso, o processo deve ser identico.
A rotina em C é utilizada pelo legado e as novas aplicações estão sendo feitas em Java / J2EE. Não gostaria de no java ter que utilizar uma dll em C para fazer esse processo já que o Java nos fornece toda a API de criptografia.

Vou passar um código C completo que fiz para testar e o código Java.
O que estou tentando fazer agora é obter a Session Key no Java identica ao C. No C, a session key é gerada pela api CryptDeriveKey.
Na documentação da CryptDeriveKey no MSDN tem uma explicação de como é feita a derivação da chave.

Senha e dados utilizados nos testes:
Password = "test password"
Data = "The book is on the table"

Resultado no programa C:
Hash da senha = 2C EB 02 A8 5F 6D 4D E6 C2 8B 2E 59 FD A8 86 D5 26 DA FB 0D
Hash da session key = 12 93 D7 12 C2 CD D2 79 1D 43 2F CE 58 C4 33 CA 31 41 AA 77
(A session key não pode ser visualizada diretamente, apenas o seu hash)

No programa Java:
Hash da senha = 2C EB 02 A8 5F 6D 4D E6 C2 8B 2E 59 FD A8 86 D5 26 DA FB 0D
Hash da session key = 42 85 CA D4 84 9B 13 34 74 B5 EF 19 04 73 D6 6F 0C EF EA 99

Como podem ver, o hash da senha é identico mas o da session key não.

O código em C utilizado para o teste é:

#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>

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");
}

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);
}

O código em Java utilizado é:

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];
		for (int i = 0; i < size; i++) {
			if(i < buffer1.length)
				derivedKey[i] = buffer1[i];
			else	
				derivedKey[i] = buffer2[i - buffer1.length];	
		}

		return derivedKey;
	}
}