I am currently working on what we call “centralized enrolment” that is:
Issue a keypair either RSA or EC on a HSM;
Issue a symmetric session key on the same HSM;
Wrap the private key with the session key using various mechanisms such as CKM_AES_CBC_PAD, CKM_AES_KEY_WRAP_PAD and CKM_AES_KEY_WRAP;
Wrap the session key with an external RSA master key which is provided to the HSM;
Return both protected keys.
When verifying the results, I succeed in unwrapping the session key (with the private key I own) but I am facing some difficulties in unwrapping the private key, when the mechanism is CKM_AES_KEY_WRAP. Everything works well with he other two.
As the session key is used only once, we let the HSM decide which IV to use (in the case of CKM_AES_CBC_PAD, it will be a 16 byte array of zeros).
What works well is:
case CKM_AES_KEY_WRAP_PAD -> {
Cipher wrapper = Cipher.getInstance("AESWrapPad", "BC");
wrapper.init(Cipher.UNWRAP_MODE, this.clearSecretKey);
clearPrivateKey = (PrivateKey) wrapper.unwrap(privateKeyToRecover, algorithm, Cipher.PRIVATE_KEY);
}
and
case CKM_AES_CBC_PAD -> {
byte[] ivb = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
Cipher wrapper = Cipher.getInstance("AES/CBC/Pkcs7Padding", "BC");
wrapper.init(Cipher.UNWRAP_MODE, this.clearSecretKey, new IvParameterSpec(ivb));
clearPrivateKey = (PrivateKey) wrapper.unwrap(privateKeyToRecover, algorithm, Cipher.PRIVATE_KEY);
}
What fails is:
case CKM_AES_KEY_WRAP -> {
Cipher wrapper = Cipher.getInstance("AESWrap", "BC");
wrapper.init(Cipher.UNWRAP_MODE, this.clearSecretKey);
clearPrivateKey = (PrivateKey) wrapper.unwrap(privateKeyToRecover, algorithm, Cipher.PRIVATE_KEY);
}
with the error: Unknown key type encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream.
Using the default IV specified in RFC 3394 (which is A[0] = IV = A6A6A6A6A6A6A6A6) does not seem to solve the problem.
Could someone explain me how to fix this?
Thanks a lot for reading and taking time to answer
Éric
Related
Discalaimer, I'm very new to Golang as I used the following article as the basis for this https://blog.gopheracademy.com/advent-2015/libc-hooking-go-shared-libraries/
I'm trying to write an LD_PRELOAD library that would intercept calls to SSL_read and SSL_write of the OpenSSL library.
This is the code I've come up with so far:
package main
import (
"C"
"log"
"fmt"
"github.com/rainycape/dl"
)
// main is required to build a shared library, but does nothing
func main() {}
//export SSL_read
func SSL_read(s C.int, b []byte, i C.int) C.int{
lib, err := dl.Open("libssl", 0)
if err != nil {
log.Fatalln(err)
}
defer lib.Close()
var oldSSL_read func(s C.int, b []byte, i C.int) C.int
lib.Sym("SSL_read", &oldSSL_read)
res := oldSSL_read(s, b, i)
//buf := *(*[]byte)(b)
//fmt.Println(i)
return res
}
/*
//export SSL_write
func SSL_write(){
}
*/
I'm compiling the code this way:
>$ go build -buildmode=c-shared -o preload.so golang_preload.g
And testing it with openssl s_client:
LD_PRELOAD=./preload.so openssl s_client -host www.google.com -port 443
This is however the errors it is causing:
fatal error: unexpected signal during runtime execution
[signal 0xb code=0x1 addr=0x4 pc=0x7f1d91bdeee9]
runtime stack:
runtime.throw(0x7f1d92964580, 0x2a)
/usr/lib/go/src/runtime/panic.go:530 +0x92
runtime.sigpanic()
/usr/lib/go/src/runtime/sigpanic_unix.go:12 +0x5e
goroutine 17 [syscall, locked to thread]:
runtime.cgocall(0x7f1d926d4f30, 0xc82003e930, 0xc800000000)
/usr/lib/go/src/runtime/cgocall.go:123 +0x121 fp=0xc82003e8e0 sp=0xc82003e8b0
github.com/rainycape/dl._Cfunc_call(0x7f1d9231e970, 0xc82008c160, 0xc8200762a0, 0x3, 0xc82008a038, 0xc800000000)
github.com/rainycape/dl/_obj/_cgo_gotypes.go:83 +0x43 fp=0xc82003e930 sp=0xc82003e8e0
github.com/rainycape/dl.makeTrampoline.func1(0xc82008e280, 0x3, 0x3, 0x0, 0x0, 0x0)
/home/asm/gocode/src/github.com/rainycape/dl/trampoline.go:124 +0x8b6 fp=0xc82003ebf8 sp=0xc82003e930
reflect.callReflect(0xc82008c120, 0xc82003edf0)
/usr/lib/go/src/reflect/value.go:508 +0x2cd fp=0xc82003edd8 sp=0xc82003ebf8
reflect.makeFuncStub(0xc8025cec30, 0x4, 0x25c5b70, 0x25c3b60, 0xc8025c3b60, 0x0, 0x0, 0xc820076260, 0xc82008a030, 0x0, ...)
/usr/lib/go/src/reflect/asm_amd64.s:17 +0x38 fp=0xc82003edf0 sp=0xc82003edd8
main.SSL_read(0x25cec30, 0x4, 0x25c5b70, 0x25c3b60, 0x7f1d025c3b60, 0xc800000000)
/tmp/golang_preload.go:44 +0x1e5 fp=0xc82003ee88 sp=0xc82003edf0
main._cgoexpwrap_24df11e45e4b_SSL_read(0x25cec30, 0x4, 0x25c5b70, 0x25c3b60, 0x25c3b60, 0x25c3b60)
command-line-arguments/_obj/_cgo_gotypes.go:68 +0x47 fp=0xc82003eec0 sp=0xc82003ee88
runtime.call64(0x0, 0x7fffb5954988, 0x7fffb5954a10, 0x30)
/usr/lib/go/src/runtime/asm_amd64.s:473 +0x40 fp=0xc82003ef08 sp=0xc82003eec0
runtime.cgocallbackg1()
/usr/lib/go/src/runtime/cgocall.go:267 +0x110 fp=0xc82003ef40 sp=0xc82003ef08
runtime.cgocallbackg()
/usr/lib/go/src/runtime/cgocall.go:180 +0xd9 fp=0xc82003efa0 sp=0xc82003ef40
runtime.cgocallback_gofunc(0x0, 0x0, 0x0)
/usr/lib/go/src/runtime/asm_amd64.s:716 +0x5d fp=0xc82003efb0 sp=0xc82003efa0
runtime.goexit()
/usr/lib/go/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc82003efb8 sp=0xc82003efb0
goroutine 34 [syscall, locked to thread]:
runtime.goexit()
/usr/lib/go/src/runtime/asm_amd64.s:1998 +0x1
What is the proper way to access buffer in the SSL_read function, I've attempted unsafe.Pointer but I have can't bind type to value error.
update:
The SSL struct is defined in openssl.h. Adding that import results in conflict with SSL_read function.
In pure C code, using simple void* pointer would be enough, replacing it with unsafe.Pointer for the SSL and buffer results in the following error:
panic: can't bind value of type unsafe.Pointer
goroutine 17 [running, locked to thread]:
panic(0x7f0a8e933400, 0xc820096060)
/usr/lib/go/src/runtime/panic.go:464 +0x3ea
github.com/rainycape/dl.makeTrampoline.func1(0xc8200980f0, 0x3, 0x3, 0x0, 0x0, 0x0)
/home/asm/gocode/src/github.com/rainycape/dl/trampoline.go:116 +0x186e
reflect.callReflect(0xc82009c040, 0xc82004be10)
/usr/lib/go/src/reflect/value.go:508 +0x2cd
reflect.makeFuncStub(0xa12bd0, 0xa07b00, 0x400, 0x7f0a8e907800, 0xc82009a000, 0x0, 0x0, 0xc820096000, 0xc82009a000, 0x0, ...)
/usr/lib/go/src/reflect/asm_amd64.s:17 +0x38
main.SSL_read(0xa12bd0, 0xa07b00, 0xc800000400, 0x7f0a00000000)
/tmp/preload.go:26 +0x1cd
main._cgoexpwrap_613180a44973_SSL_read(0xa12bd0, 0xa07b00, 0x400, 0xa07b00)
command-line-arguments/_obj/_cgo_gotypes.go:50 +0x35
[]byte is a Go slice, which you can't use in C. The signature for SSL_read is:
SSL_read(SSL *ssl, void *buf, int num)
I order for the call to work, you need to match the signature and use equivalent types in your function definition.
func SSL_read(ssl *C.SSL, buf unsafe.Pointer, num C.int)
I have the following array :
static const int8_t ARRAY[8] = {0x18, 0xB8, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0};
and I need to get an integer value. For this ARRAY final int value is 13547544, because the bytes in the array, followed by in the opposite order little-endian.
Example 0xCEB818 = 13547544
How can I do this?
Can have ready standard solutions?
Thanks in advance!
Since the host platform is little-endian, you can just point a 64-bit integer pointer at the array and the machine will read the array correctly.
static const int8_t ARRAY[8] = {0x18, 0xB8, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0};
uint64_t *value = (uint64_t *)&ARRAY;
NSLog(#"%llu", *value); // outputs 13547544
I'm having trouble calculating the MAC of the finished message.The RFC gives the formula
HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
TLSCompressed.version + TLSCompressed.length +
TLSCompressed.fragment));
But the tlsCompressed(tlsplaintext in this case because no compression is used) does not contain version information:(hex dump)
14 00 00 0c 2c 93 e6 c5 d1 cb 44 12 bd a0 f9 2d
the first byte is the tlsplaintext.type, followed by uint24 length.
The full message, with the MAC and padding appended and before encryption is
1400000c2c93e6c5d1cb4412bda0f92dbc175a02daab04c6096da8d4736e7c3d251381b10b
I have tried to calculate the hmac with the following parameters(complying to the rfc) but it does not work:
uint64 seq_num
uint8 tlsplaintext.type
uint8 tlsplaintext.version_major
uint8 tlscompressed.version_minor
uint16 tlsplaintext.length
opaque tlsplaintext.fragment
I have also tried omitting the version and using uint24 length instead.no luck.
My hmac_hash() function cannot be the problem because it has worked thus far. I am also able to compute the verify_data and verify it.
Because this is the first message sent under the new connection state, the sequence number is 0.
So, what exactly are the parameters for the calculation of the MAC for the finished message?
Here's the relevant source from Forge (JS implementation of TLS 1.0):
The HMAC function:
var hmac_sha1 = function(key, seqNum, record) {
/* MAC is computed like so:
HMAC_hash(
key, seqNum +
TLSCompressed.type +
TLSCompressed.version +
TLSCompressed.length +
TLSCompressed.fragment)
*/
var hmac = forge.hmac.create();
hmac.start('SHA1', key);
var b = forge.util.createBuffer();
b.putInt32(seqNum[0]);
b.putInt32(seqNum[1]);
b.putByte(record.type);
b.putByte(record.version.major);
b.putByte(record.version.minor);
b.putInt16(record.length);
b.putBytes(record.fragment.bytes());
hmac.update(b.getBytes());
return hmac.digest().getBytes();
};
The function that creates the Finished record:
tls.createFinished = function(c) {
// generate verify_data
var b = forge.util.createBuffer();
b.putBuffer(c.session.md5.digest());
b.putBuffer(c.session.sha1.digest());
// TODO: determine prf function and verify length for TLS 1.2
var client = (c.entity === tls.ConnectionEnd.client);
var sp = c.session.sp;
var vdl = 12;
var prf = prf_TLS1;
var label = client ? 'client finished' : 'server finished';
b = prf(sp.master_secret, label, b.getBytes(), vdl);
// build record fragment
var rval = forge.util.createBuffer();
rval.putByte(tls.HandshakeType.finished);
rval.putInt24(b.length());
rval.putBuffer(b);
return rval;
};
The code to handle a Finished message is a bit lengthier and can be found here. I see that I have a comment in that code that sounds like it might be relevant to your problem:
// rewind to get full bytes for message so it can be manually
// digested below (special case for Finished messages because they
// must be digested *after* handling as opposed to all others)
Does this help you spot anything in your implementation?
Update 1
Per your comments, I wanted to clarify how TLSPlainText works. TLSPlainText is the main "record" for the TLS protocol. It is the "wrapper" or "envelope" for content-specific types of messages. It always looks like this:
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;
So it always has a version. A Finished message is a type of handshake message. All handshake messages have a content type of 22. A handshake message looks like this:
struct {
HandshakeType msg_type;
uint24 length;
body
} Handshake;
A Handshake message is yet another envelope/wrapper for other messages, like the Finished message. In this case, the body will be a Finished message (HandshakeType 20), which looks like this:
struct {
opaque verify_data[12];
} Finished;
To actually send a Finished message, you have to wrap it up in a Handshake message envelope, and then like any other message, you have to wrap it up in a TLS record (TLSPlainText). The ultimate result looks/represents something like this:
struct {
ContentType type=22;
ProtocolVersion version=<major, minor>;
uint16 length=<length of fragment>;
opaque fragment=<struct {
HandshakeType msg_type=20;
uint24 length=<length of finished message>;
body=<struct {
opaque verify_data[12]>;
} Finished>
} Handshake>
} TLSPlainText;
Then, before transport, the record may be altered. You can think of these alterations as operations that take a record and transform its fragment (and fragment length). The first operation compresses the fragment. After compression you compute the MAC, as described above and then append that to the fragment. Then you encrypt the fragment (adding the appropriate padding if using a block cipher) and replace it with the ciphered result. So, when you're finished, you've still got a record with a type, version, length, and fragment, but the fragment is encrypted.
So, just so we're clear, when you're computing the MAC for the Finished message, imagine passing in the above TLSPlainText (assuming there's no compression as you indicated) to a function. This function takes this TLSPlainText record, which has properties for type, version, length, and fragment. The HMAC function above is run on the record. The HMAC key and sequence number (which is 0 here) are provided via the session state. Therefore, you can see that everything the HMAC function needs is available.
In any case, hopefully this better explains how the protocol works and that will maybe reveal what's going wrong with your implementation.
How can I use OpenSSL's ECC support to encrypt or decrypt a text string? I am able to generate ECC private/public keys using OpenSSL APIs, but I don't know how to encrypt plain text using those keys.
Since its so hard to find examples showing how to use ECC to encrypt data I thought I'd post some code for others to use. For the complete listing, check out my openssl-dev posting:
http://www.mail-archive.com/openssl-dev#openssl.org/msg28042.html
Basically its a flushed out usable version of how to use ECDH to secure a block of data. ECDH is used to generate a shared secret. The shared secret is then hashed using SHA 512. The resulting 512 bits are split up, with 256 serving as the key to the symmetric cipher (AES 256 in my example) and the other 256 bits used as the key for the HMAC. My implementation is loosely based on the ECIES standard outlined by SECG working group.
The key functions are ecies_encrypt() which accepts the public key in hex form and returns the encrypted data:
secure_t * ecies_encrypt(char *key, unsigned char *data, size_t length) {
void *body;
HMAC_CTX hmac;
int body_length;
secure_t *cryptex;
EVP_CIPHER_CTX cipher;
unsigned int mac_length;
EC_KEY *user, *ephemeral;
size_t envelope_length, block_length, key_length;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], block[EVP_MAX_BLOCK_LENGTH];
// Simple sanity check.
if (!key || !data || !length) {
printf("Invalid parameters passed in.\n");
return NULL;
}
// Make sure we are generating enough key material for the symmetric ciphers.
if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH) {
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
}
// Convert the user's public key from hex into a full EC_KEY structure.
if (!(user = ecies_key_create_public_hex(key))) {
printf("Invalid public key provided.\n");
return NULL;
}
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create())) {
printf("An error occurred while trying to generate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
}
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(user), ephemeral, ecies_key_derivation) != SHA512_DIGEST_LENGTH) {
printf("An error occurred while trying to compute the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// Determine the envelope and block lengths so we can allocate a buffer for the result.
else if ((block_length = EVP_CIPHER_block_size(ECIES_CIPHER)) == 0 || block_length > EVP_MAX_BLOCK_LENGTH || (envelope_length = EC_POINT_point2oct(EC_KEY_get0_group(
ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, NULL, 0, NULL)) == 0) {
printf("Invalid block or envelope length. {block = %zu / envelope = %zu}\n", block_length, envelope_length);
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// We use a conditional to pad the length if the input buffer is not evenly divisible by the block size.
else if (!(cryptex = secure_alloc(envelope_length, EVP_MD_size(ECIES_HASHER), length, length + (length % block_length ? (block_length - (length % block_length)) : 0)))) {
printf("Unable to allocate a secure_t buffer to hold the encrypted result.\n");
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// Store the public key portion of the ephemeral key.
else if (EC_POINT_point2oct(EC_KEY_get0_group(ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, secure_key_data(cryptex), envelope_length,
NULL) != envelope_length) {
printf("An error occurred while trying to record the public portion of the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
secure_free(cryptex);
return NULL;
}
// The envelope key has been stored so we no longer need to keep the keys around.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// For now we use an empty initialization vector.
memset(iv, 0, EVP_MAX_IV_LENGTH);
// Setup the cipher context, the body length, and store a pointer to the body buffer location.
EVP_CIPHER_CTX_init(&cipher);
body = secure_body_data(cryptex);
body_length = secure_body_length(cryptex);
// Initialize the cipher with the envelope key.
if (EVP_EncryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_EncryptUpdate(&cipher, body,
&body_length, data, length - (length % block_length)) != 1) {
printf("An error occurred while trying to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Check whether all of the data was encrypted. If they don't match up, we either have a partial block remaining, or an error occurred.
else if (body_length != length) {
// Make sure all that remains is a partial block, and their wasn't an error.
if (length - body_length >= block_length) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Copy the remaining data into our partial block buffer. The memset() call ensures any extra bytes will be zero'ed out.
memset(block, 0, EVP_MAX_BLOCK_LENGTH);
memcpy(block, data + body_length, length - body_length);
// Advance the body pointer to the location of the remaining space, and calculate just how much room is still available.
body += body_length;
if ((body_length = secure_body_length(cryptex) - body_length) < 0) {
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Pass the final partially filled data block into the cipher as a complete block. The padding will be removed during the decryption process.
else if (EVP_EncryptUpdate(&cipher, body, &body_length, block, block_length) != 1) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
}
// Advance the pointer, then use pointer arithmetic to calculate how much of the body buffer has been used. The complex logic is needed so that we get
// the correct status regardless of whether there was a partial data block.
body += body_length;
if ((body_length = secure_body_length(cryptex) - (body - secure_body_data(cryptex))) < 0) {
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
else if (EVP_EncryptFinal_ex(&cipher, body, &body_length) != 1) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
EVP_CIPHER_CTX_cleanup(&cipher);
// Generate an authenticated hash which can be used to validate the data during decryption.
HMAC_CTX_init(&hmac);
mac_length = secure_mac_length(cryptex);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, secure_mac_data(cryptex), &mac_length) != 1) {
printf("Unable to generate a data authentication code. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
secure_free(cryptex);
return NULL;
}
HMAC_CTX_cleanup(&hmac);
return cryptex;
}
And ecies_decrypt() which takes the private key, again in hex form, and decrypts the previously secured buffer:
unsigned char * ecies_decrypt(char *key, secure_t *cryptex, size_t *length) {
HMAC_CTX hmac;
size_t key_length;
int output_length;
EVP_CIPHER_CTX cipher;
EC_KEY *user, *ephemeral;
unsigned int mac_length = EVP_MAX_MD_SIZE;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], md[EVP_MAX_MD_SIZE], *block, *output;
// Simple sanity check.
if (!key || !cryptex || !length) {
printf("Invalid parameters passed in.\n");
return NULL;
}
// Make sure we are generating enough key material for the symmetric ciphers.
else if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH) {
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
}
// Convert the user's public key from hex into a full EC_KEY structure.
else if (!(user = ecies_key_create_private_hex(key))) {
printf("Invalid private key provided.\n");
return NULL;
}
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create_public_octets(secure_key_data(cryptex), secure_key_length(cryptex)))) {
printf("An error occurred while trying to recreate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
}
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(ephemeral), user, ecies_key_derivation) != SHA512_DIGEST_LENGTH) {
printf("An error occurred while trying to compute the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// The envelope key material has been extracted, so we no longer need the user and ephemeral keys.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// Use the authenticated hash of the ciphered data to ensure it was not modified after being encrypted.
HMAC_CTX_init(&hmac);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, md, &mac_length) != 1) {
printf("Unable to generate the authentication code needed for validation. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
return NULL;
}
HMAC_CTX_cleanup(&hmac);
// We can use the generated hash to ensure the encrypted data was not altered after being encrypted.
if (mac_length != secure_mac_length(cryptex) || memcmp(md, secure_mac_data(cryptex), mac_length)) {
printf("The authentication code was invalid! The ciphered data has been corrupted!\n");
return NULL;
}
// Create a buffer to hold the result.
output_length = secure_body_length(cryptex);
if (!(block = output = malloc(output_length + 1))) {
printf("An error occurred while trying to allocate memory for the decrypted data.\n");
return NULL;
}
// For now we use an empty initialization vector. We also clear out the result buffer just to be on the safe side.
memset(iv, 0, EVP_MAX_IV_LENGTH);
memset(output, 0, output_length + 1);
EVP_CIPHER_CTX_init(&cipher);
// Decrypt the data using the chosen symmetric cipher.
if (EVP_DecryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_DecryptUpdate(&cipher, block,
&output_length, secure_body_data(cryptex), secure_body_length(cryptex)) != 1) {
printf("Unable to decrypt the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
block += output_length;
if ((output_length = secure_body_length(cryptex) - output_length) != 0) {
printf("The symmetric cipher failed to properly decrypt the correct amount of data!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
if (EVP_DecryptFinal_ex(&cipher, block, &output_length) != 1) {
printf("Unable to decrypt the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
EVP_CIPHER_CTX_cleanup(&cipher);
*length = secure_orig_length(cryptex);
return output;
}
I'm posting this because I personally couldn't find any other examples of how to secure files using ECC and the OpenSSL library. That said its worth mentioning alternatives that don't use OpenSSL. One is seccure which follows a pattern similar to my example, only it relies libgcrypt. Since libgcrypt doesn't provide all of the underlying ECC functions needed, the seccure program fills in the gaps and implements the ECC logic missing from libgcrypt.
Another program worth looking at is SKS, which uses a similar ECC based encryption process as the example above, but doesn't have any external dependencies (so all the ECC code is right there for you to look at).
ECC itself doesn't really define any encryption/decryption operations - algorithms built on elliptic curves do.
One example is Elliptic-Curve Diffie-Hellman. You could encrypt a message using ECDH by:
Generating an ephemeral EC key.
Using that key and the public key of the recipient, generate a secret using ECDH.
Use that secret as a key to encrypt the message with a symmetric cipher, like AES.
Transmit the encrypted message and the ephemeral public key generated in step 1.
To decrypt:
Load the ephemeral public key from the message.
Use that public key together with your recipient private key to generate a secret using ECDH.
Use that secret as a key to decrypt the message with the symmetric cipher.
EDIT: The following is the basic idea to generate a secret using ECDH. First we need to define a key derivation function - this one uses the SHA1 hash.
void *KDF1_SHA1(const void *in, size_t inlen, void *out, size_t *outlen)
{
if (*outlen < SHA_DIGEST_LENGTH)
return NULL;
else
*outlen = SHA_DIGEST_LENGTH;
return SHA1(in, inlen, out);
}
This is the ECDH code for the sender side. It assumes that the recipient's public key is already in "recip_key", and you have verified it with EC_KEY_check_key(). It also omits much important error checking, for the sake of brevity, which you will definitely want to include in production code.
EC_KEY *ephemeral_key = NULL;
const EC_GROUP *group = NULL;
unsigned char buf[SHA_DIGEST_LENGTH] = { 0 };
group = EC_KEY_get0_group(recip_key);
ephemeral_key = EC_KEY_new();
EC_KEY_set_group(ephemeral_key, group);
EC_KEY_generate_key(ephemeral_key);
ECDH_compute_key(buf, sizeof buf, EC_KEY_get0_public_key(recip_key), ephemeral_key, KDF1_SHA1);
After this the buffer 'buf' contains 20 bytes of material you can use for keying. This abbreviated example is based on the code in "ecdhtest.c" in the openssl source distribution - I suggest taking a look at it.
You will want to send the public key portion of ephemeral_key with the encrypted message, and securely discard the private key portion. A MAC over the data is also a good idea, and if you need more than 20 bytes of keying material a longer hash is probably in order.
The recipient does something similar, except that its private key already exists (since the sender had to know the corresponding public key beforehand), and the public key is recieved from the sender.
I need to have a Win32 application load a hard coded AES-256 key, ideally using the WinCrypt.h methods. I've got my key in an unsigned char[32] but I can't find the correct format of a key blob to pass to CryptImportKey. Everything seems to give me invalid parameter errors. Is there any way to do this?
(Also important is how to set IV in WinCrypt. I can't see how to do that at all)
Solved it. I was using the wrong bType and using 256 for keySize instead of 32.
BYTE myPrivateKey[] =
{1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,
31,32};
BYTE myIV[] =
{1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16};
struct aes256keyBlob
{
BLOBHEADER hdr;
DWORD keySize;
BYTE bytes[32];
} blob;
blob.hdr.bType = PLAINTEXTKEYBLOB;
blob.hdr.bVersion = CUR_BLOB_VERSION;
blob.hdr.reserved = 0;
blob.hdr.aiKeyAlg = CALG_AES_256;
blob.keySize = 32;
memcpy(blob.bytes, myPrivateKey, 32);
HCRYPTKEY hKey;
if (CryptImportKey(hCryptProv, (BYTE*)&blob, sizeof(aes256keyBlob), NULL, 0, &hKey))
{
if(CryptSetKeyParam(hKey, KP_IV, myIV, 0))
{
//do decryption here
}
else{/*error*/}
CryptDestroyKey(hKey);
}
else{/*error*/}