Conversão de rotina de criptografia em C para Java

29 respostas
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);
...

29 Respostas

T

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

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

LeoNicolas

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.

T

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

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

LeoNicolas

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.

T

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

T

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.

Luca

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)

T

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 )

LeoNicolas

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.

Luca

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

LeoNicolas

Luca

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

Luca

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

LeoNicolas

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

T

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

LeoNicolas

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?

LeoNicolas

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
louds

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…

Luca

Olá

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

[]s
Luca

LeoNicolas

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

Thingol

Conforme vc me informou, a chave do 3DES é de 21 bytes. Adicionando os bits de paridade fica com 24 bytes.
Implementei o metodo que adiciona os bits de paridade e ainda estou com os resultados diferentes no Java dos obtidos no C.

Será que vc tem mais alguma idéia?

Segue abaixo o código completo em Java e em C.

A senha utilizada é “test password” e os dados criptorafados são “The book is on the table”

Estou passando o IV zerado.

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++) {
			// Adiciona a sobra anterior no início do próximo byte			
			b = (byte) ((sobra << 8 - deslocamento) | ((key[i] & 0xFF) >> deslocamento));

			// Obtém a nova sobra
			sobra = (byte) (key[i] & (0x0FF >> (7 - deslocamento++)));

			// Adiciona o bit de paridade na saída
			keyData[i + j] = addParityBit(b);

			// Calcula o oitavo byte em cima da sobra do setimo byte
			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());
	}
}

======================================
Segue o código de teste em C

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

	CHAR szPassword[14] = "test password";
	CHAR szDataIn[25] = "The book is on the table";

	DWORD dwPassLen = strlen(szPassword);
	DWORD dwDataInLen = strlen(szDataIn);

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


	/*DWORD tamHash2 = 0;
	if(!CryptGetHashParam(hHash, HP_HASHVAL, NULL, &tamHash2, 0)) {
		MyHandleError("Bla bla");
	}

	BYTE * pbHash2 = new BYTE[tamHash2 + 1];
	memset(pbHash2, 0, tamHash2 + 1);
	if(!CryptGetHashParam(hHash, HP_HASHVAL, pbHash2, &tamHash2, 0)) {
		MyHandleError("Bla bla");
	}*/

	//--------------------------------------------------------------------
	// Create a session key based on the hash of the password.
	if(!CryptDeriveKey(hCryptProv, CALG_3DES, hHash, CRYPT_EXPORTABLE, &hKey)) {
		MyHandleError("Error during CryptDeriveKey!");
	}


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

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

Puxa, agora não sei mais. Estou sem tempo para testar eu mesmo; é que se eu mesmo tentasse (começando do zero), talvez evitasse algum erro que você estivesse porventura cometendo. Será que alguém mais conseguiu fazer isso? talvez fosse interessante ver se alguém conseguiu fazer a interoperabilidade entre MS CryptoAPI e OpenSSL ou outro pacote (como a CryptLib), só para ver o que está errado).

LeoNicolas

Pessoal,

boas novas…
Consegui fazer a criptografia em C e descriptografia em Java.

Agradeço a ajuda de todos.

Muito obrigado

:smiley:

Luca

Olá

Viva! :lol:

Agora vc não deixar a gente morrer de curiosidade. Em nome do tempo que dedicamos a ele, descreva como fez para resolver o problema.

[]s
Luca

LeoNicolas

Segue o código completo

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

	// Hash data
	public static final int HASH_TYPE_MD5 = 1;
	public static final int HASH_TYPE_SHA1 = 2;

	public static final int HASH_LENGTH_MD5 = 16;
	public static final int HASH_LENGTH_SHA1 = 20;

	private final String HASH_ALGORITHM_NAME_MD5 = "MD5";
	private final String HASH_ALGORITHM_NAME_SHA1 = "SHA-1";

	// Crypt data
	public static final int CRYPT_TYPE_DES = 3;
	public static final int CRYPT_TYPE_3DES = 4;
	public static final int CRYPT_TYPE_RC2 = 5;
	public static final int CRYPT_TYPE_RC4 = 6;

	private final String CRYPT_ALGORITHM_NAME_DES = "DES";
	private final String CRYPT_ALGORITHM_NAME_3DES = "DESede";
	private final String CRYPT_ALGORITHM_NAME_RC2 = "RC2";
	private final String CRYPT_ALGORITHM_NAME_RC4 = "RC4";

	private final int CRYPT_KEY_LENGTH_DES = 8;
	private final int CRYPT_KEY_LENGTH_3DES = 24; 	// 168 bits + parity   
	private final int CRYPT_KEY_LENGTH_RC2RC4 = 16; // 128 bits + parity

	public byte[] calcHash(byte[] data, int algorithmType) throws NoSuchAlgorithmException {

		MessageDigest md = null;
		if (HASH_TYPE_MD5 == algorithmType)
			md = MessageDigest.getInstance(HASH_ALGORITHM_NAME_MD5);
		else if (HASH_TYPE_SHA1 == algorithmType)
			md = MessageDigest.getInstance(HASH_ALGORITHM_NAME_SHA1);
		else
			throw new NoSuchAlgorithmException();

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

		return bufferSaida;
	}

	public byte[] crypt(byte[] data, String key, int cryptAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
		// Valor padrão para hash de chave no BSCripto
		return crypt(data, key, cryptAlgorithmType, HASH_TYPE_SHA1);
	}

	public byte[] crypt(byte[] data, String key, int cryptAlgorithmType, int keyHashAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
		return cryptDecrypt(data, key, cryptAlgorithmType, keyHashAlgorithmType, Cipher.ENCRYPT_MODE);
	}

	public byte[] decrypt(byte[] data, String key, int cryptAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
		// Valor padrão para hash de chave no BSCripto
		return crypt(data, key, cryptAlgorithmType, HASH_TYPE_SHA1);
	}

	public byte[] decrypt(byte[] data, String key, int cryptAlgorithmType, int keyHashAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
		return cryptDecrypt(data, key, cryptAlgorithmType, keyHashAlgorithmType, Cipher.DECRYPT_MODE);
	}

	private byte[] cryptDecrypt(byte[] data, String key, int cryptAlgorithmType, int hashAlgorithmType, int encryptMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
		byte[] hashKey = null;
		Cipher cipher = null;
		String algorithmName = getCryptAlgorithmName(cryptAlgorithmType);

		// Obtém o hash da chave
		hashKey = calcHash(key.getBytes(), hashAlgorithmType);

		// Obtém uma chave com o tamanho correto para o algoritmo de criptografia escolhido
		byte[] derivedKey = cryptDeriveKey(hashKey, cryptAlgorithmType, hashAlgorithmType);

		// Gera um vetor de inicialização zerado igual ao utilizado na CryptoAPI
		byte[] iv = new byte[8];
		Arrays.fill(iv, (byte) 0x00);
		IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

		// Gera a chave secreta para o algoritmo de criptografia escolhido
		SecretKeySpec secretKeySpec = new SecretKeySpec(derivedKey, algorithmName);

		// Criptografa os dados		
		cipher = Cipher.getInstance(algorithmName + "/CBC/PKCS5Padding");
		cipher.init(encryptMode, secretKeySpec, ivParameterSpec);
		byte[] cryptData = cipher.doFinal(data);

		return cryptData;
	}

	private byte[] cryptDeriveKey(byte[] key, int cryptAlgorithmType, int hashAlgorithmType) throws NoSuchAlgorithmException {
		byte[] derivedKey;

		switch (cryptAlgorithmType) {
			case CRYPT_TYPE_3DES :
				derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_3DES, hashAlgorithmType, true);
				break;
			case CRYPT_TYPE_DES :
				derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_DES, hashAlgorithmType, true);
				break;
			case CRYPT_TYPE_RC2 :
			case CRYPT_TYPE_RC4 :
				derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_RC2RC4, hashAlgorithmType, true);
				break;
			default :
				throw new NoSuchAlgorithmException();
		}
		return derivedKey;
	}

	private byte[] cryptDeriveKey(byte[] key, int keySize, int hashAlgorithmType, boolean parity) throws NoSuchAlgorithmException {
		byte[] buffer1 = new byte[64];
		byte[] buffer2 = new byte[64];
		byte[] derivedKey = new byte[keySize];

		// Caso não necessite de derivação retorna a chave sem derivação.
		// A derivação é feita apenas quando o tamanho da chave para o algoritmo
		// de criptografia escolhido é maior que a chave recebida
		if (((HASH_TYPE_MD5 == hashAlgorithmType) && (keySize <= HASH_LENGTH_MD5)) || ((HASH_TYPE_SHA1 == hashAlgorithmType) && (keySize <= HASH_LENGTH_SHA1))) {
			for (int i = 0; i < keySize; i++)
				derivedKey[i] = key[i];
			return derivedKey;
		}

		// 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
		buffer1 = calcHash(buffer1, hashAlgorithmType);
		buffer2 = calcHash(buffer2, hashAlgorithmType);

		// Transporta os bytes dos buffers para a chave final até o tamanho desejado
		for (int i = 0; i < keySize; i++) {
			if (i < buffer1.length)
				derivedKey[i] = buffer1[i];
			else
				derivedKey[i] = buffer2[i - buffer1.length];
		}

		// Adiciona paridade
		if (true == parity)
			return addParityBit(derivedKey);
		else
			return derivedKey;
	}

	private byte[] addParityBit(byte[] key) {
		byte[] keyData = new byte[key.length];

		for (short i = 0; i < key.length; i++) {
			byte b = key[i];
			byte paridade = (byte) ((b >> 1) ^ (b >> 2) ^ (b >> 3) ^ (b >> 4) ^ (b >> 5) ^ (b >> 6) ^ (b >> 7));
			keyData[i] = (byte) ((b & 0xFE) | (paridade & 0x01));
		}
		return keyData;
	}

	private String getCryptAlgorithmName(int cryptAlgorithmType) throws NoSuchAlgorithmException {
		String algorithmName;

		switch (cryptAlgorithmType) {
			case CRYPT_TYPE_3DES :
				algorithmName = CRYPT_ALGORITHM_NAME_3DES;
				break;
			case CRYPT_TYPE_DES :
				algorithmName = CRYPT_ALGORITHM_NAME_DES;
				break;
			case CRYPT_TYPE_RC2 :
				algorithmName = CRYPT_ALGORITHM_NAME_RC2;
				break;
			case CRYPT_TYPE_RC4 :
				algorithmName = CRYPT_ALGORITHM_NAME_RC4;
				break;
			default :
				throw new NoSuchAlgorithmException();
		}
		return algorithmName;
	}
}
LeoNicolas

Só uma observação.

A criptografia com RC2 e RC4 ainda não estão funcionando.

Estou corrigindo agora.

Luca

Olá

Mas o que realmente estava errado? O código ou o conceito?

[]s
Luca

LeoNicolas

Conforme o thingol havia passado, tive que acertar o Mode (CBC - Cipher Block Chaining Mode) e o Padding (PKCS5Padding).

Também acertei o cálculo de paridade. Eu estava inserindo os bits de paridade na chave de 21 bytes (3DES) gerando uma de 24 bytes.
Agora gero uma de 24 bytes e altero o último bit de cada byte para o bit da paridade.

Outra coisa. Utilizei um vetor de inicialização (IV) zerado.

T

Parabéns!

S

Pessoal,

Sei que estou ressuscitando o tópico, mas preciso desta função em java.
Dei uma olhada, tentei usar o exemplo citado acima mas não consigo executar.
Alguém consegue me dar uma ajuda aí?

#define HASH_ALGORITHM	CALG_MD5
#define ENCRYPT_ALGORITHM	CALG_RC2 
#define ENCRYPT_ALGORITHM2	CALG_RC4 
#define ENCRYPT_KEY	(0x00280000 | CRYPT_EXPORTABLE)

BOOL CCrypto::Encrypt(TCHAR* szPassword, _bstr_t & sRetorno)
{
	BOOL bResult = TRUE;

	HCRYPTPROV hProv = NULL;
	HCRYPTKEY hKey = NULL;
	HCRYPTKEY hXchgKey = NULL;
	HCRYPTHASH hHash = NULL;
	DWORD dwLength;

	TCHAR szLocalPassword[] = _T("Mz6@a0i*");

	if (CryptAcquireContext(&hProv, NULL, CSP, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
	{
		if (CryptCreateHash(hProv, HASH_ALGORITHM, 0, 0, &hHash))
		{
			dwLength = sizeof(TCHAR)*_tcslen(szLocalPassword);
			if (CryptHashData(hHash, (BYTE *)szLocalPassword, dwLength, 0))
			{
				if (CryptDeriveKey(hProv, ENCRYPT_ALGORITHM2, hHash, ENCRYPT_KEY, &hKey))
				{
					dwLength = sizeof(TCHAR)*_tcslen(szPassword);
					
					BYTE *pbBuffer = (BYTE *)malloc(dwLength);
					if (pbBuffer != NULL)
					{
						memcpy(pbBuffer, szPassword, dwLength);

						if (CryptEncrypt(hKey, 0, TRUE, 0, (BYTE *)pbBuffer, &dwLength, dwLength))
						{

							BinBuffer bin((BYTE *)pbBuffer, dwLength);
							sRetorno = BinHex(bin);
							bResult = TRUE;
						}
						else
						{
							bResult = FALSE;
						}

					  free(pbBuffer);
					}
					else
					{
						bResult = FALSE;
					}
					CryptDestroyKey(hKey);
				}
				else
				{

					bResult = FALSE;
				}
			}
			else
			{

				bResult = FALSE;
			}
			CryptDestroyHash(hHash);

		}
		else
		{
			bResult = FALSE;
		}
		CryptReleaseContext(hProv, 0);
	}
	return bResult;

Valeu!!!

Criado 13 de abril de 2005
Ultima resposta 9 de mar. de 2011
Respostas 29
Participantes 5