Initial commit

This commit is contained in:
Tony Blyler 2016-02-02 12:24:44 -05:00
parent 082cfcc470
commit 45c6031836
3 changed files with 413 additions and 0 deletions

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# tblyler/go-mcrypt
This is a work in progress and currently just implements an encrypt and decrypt function that is compliant with PHP's rijndael AES 256 specification.
As one might guess, this was to replace existing PHP code that relied on rijndael AES 256.
## Requirements
* libmcrypt

253
mcrypt.go Normal file
View file

@ -0,0 +1,253 @@
package mcrypt
/*
#cgo LDFLAGS: -lmcrypt
#include <stdlib.h>
#include <string.h>
#include <mcrypt.h>
#define FAILED_MCRYPT_MODULE 1
#define INVALID_KEY_LENGTH 2
#define INVALID_IV_LENGTH 3
#define FAILED_TO_ENCRYPT_DATA 4
#define FAILED_TO_DECRYPT_DATA 5
#define INVALID_DATA_LENGTH 6
// getError convert a given error code to its string representation
const char* getError(int err) {
switch (err) {
case FAILED_MCRYPT_MODULE:
return "Failed to open mcrypt module";
case INVALID_KEY_LENGTH:
return "Invalid key length";
case INVALID_IV_LENGTH:
return "Invalid iv length";
case FAILED_TO_ENCRYPT_DATA:
return "Failed to encrypt data";
case FAILED_TO_DECRYPT_DATA:
return "Failed to decrypt data";
case INVALID_DATA_LENGTH:
return "Invalid data length";
}
return mcrypt_strerror(err);
}
// encrypt encrypt a given data set with rijndael 256
char* encrypt(void* key, int keyLength, void* iv, int ivLength, char* data, int* length, int* err) {
if (*length <= 0) {
*err = INVALID_DATA_LENGTH;
return NULL;
}
int i;
MCRYPT td = mcrypt_module_open("rijndael-256", NULL, "cbc", NULL);
if (td == MCRYPT_FAILED) {
*err = FAILED_MCRYPT_MODULE;
return NULL;
}
int requiredKeySize = mcrypt_enc_get_key_size(td);
int requiredIvSize = mcrypt_enc_get_iv_size(td);
// make sure the key and iv are the correct sizes
if (keyLength != requiredKeySize) {
*err = INVALID_KEY_LENGTH;
mcrypt_module_close(td);
return NULL;
}
if (ivLength != requiredIvSize) {
*err = INVALID_IV_LENGTH;
mcrypt_module_close(td);
return NULL;
}
*err = mcrypt_generic_init(td, key, keyLength, iv);
if (*err) {
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
return NULL;
}
// get the block size
int blockSize = mcrypt_enc_get_block_size(td);
// determine the new length if needed
int newLength = 0;
// if blockSize is greater than length, expand length to the blockSize
if (blockSize > *length) {
newLength = blockSize;
} else {
int lengthBlockMod = *length % blockSize;
if (lengthBlockMod) {
// if length is not multiple of blockSize, make it the next highest blockSize
newLength = *length - lengthBlockMod + blockSize;
} else {
// we do not need to change the length
newLength = *length;
}
}
// allocate and copy the data to the output value
char* output = malloc(sizeof *output * newLength);
// append byte zeroes to the output array if needed
for (i = *length; i < newLength; ++i) {
output[i] = 0;
}
memcpy(output, data, *length);
// update the length to the reallocated length
*length = newLength;
// loop through the output data by blockSize at a time
for (i = 0; i < *length; i += blockSize) {
// encrypt the block of output[i] plus blockSize
if (mcrypt_generic(td, output+i, blockSize)) {
*err = FAILED_TO_ENCRYPT_DATA;
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
free(output);
return NULL;
}
}
// finish up mcrypt
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
// return the encrypted data
return output;
}
// decrypt decrypt a given data set with rijndael 256
char* decrypt(void* key, int keyLength, void* iv, int ivLength, char* data, int* length, int* err) {
int i;
MCRYPT td = mcrypt_module_open("rijndael-256", NULL, "cbc", NULL);
if (td == MCRYPT_FAILED) {
*err = FAILED_MCRYPT_MODULE;
mcrypt_module_close(td);
return NULL;
}
int requiredKeySize = mcrypt_enc_get_key_size(td);
int requiredIvSize = mcrypt_enc_get_iv_size(td);
// make sure the key and iv are the correct sizes
if (keyLength != requiredKeySize) {
*err = INVALID_KEY_LENGTH;
mcrypt_module_close(td);
return NULL;
}
if (ivLength != requiredIvSize) {
*err = INVALID_IV_LENGTH;
mcrypt_module_close(td);
return NULL;
}
*err = mcrypt_generic_init(td, key, keyLength, iv);
if (*err) {
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
return NULL;
}
// get the block size
int blockSize = mcrypt_enc_get_block_size(td);
if (*length < blockSize || *length % blockSize) {
*err = INVALID_DATA_LENGTH;
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
return NULL;
}
// allocate and copy the data to the output value
char* output = malloc(sizeof *output * *length);
memcpy(output, data, *length);
// loop through the output data by blockSize at a time
for (i = 0; i < *length; i += blockSize) {
// decrypt the block of output[i] plus blockSize
if (mdecrypt_generic(td, output+i, blockSize)) {
*err = FAILED_TO_DECRYPT_DATA;
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
free(output);
return NULL;
}
}
// finish up mcrypt
mcrypt_generic_deinit(td);
mcrypt_module_close(td);
// return the decrypted data
return output;
}
*/
import "C"
import (
"errors"
"unsafe"
)
// Encrypt encrypt something with mcrypt rijndael-256 PHP-style
func Encrypt(key []byte, iv []byte, data []byte) ([]byte, error) {
// keep track of the size of the input data
length := C.int(len(data))
if length == 0 {
return nil, errors.New("Invalid data size of 0")
}
// keep track of any errors that occur on encryption
err := C.int(0)
// encrypt the data
encryptedData := C.encrypt(unsafe.Pointer(&key[0]), C.int(len(key)), unsafe.Pointer(&iv[0]), C.int(len(iv)), (*C.char)(unsafe.Pointer(&data[0])), (*C.int)(unsafe.Pointer(&length)), (*C.int)(unsafe.Pointer(&err)))
// if err is not 0, there is an error
if int(err) != 0 {
return nil, errors.New(C.GoString(C.getError(err)))
}
// ensure that memory is freed on the encrypted data after it is converted to Go bytes
defer C.free(unsafe.Pointer(encryptedData))
// return the Go bytes of the encrypted data
return C.GoBytes(unsafe.Pointer(encryptedData), length), nil
}
// Decrypt decrypt something with mcrypt rijndael-256 PHP-style
func Decrypt(key []byte, iv []byte, data []byte) ([]byte, error) {
// keep track of the size of the input data
length := C.int(len(data))
if length == 0 {
return nil, errors.New("Invalid data size of 0")
}
// keep track of any errors that occur on decryption
err := C.int(0)
// decrypt the data
decryptedData := C.decrypt(unsafe.Pointer(&key[0]), C.int(len(key)), unsafe.Pointer(&iv[0]), C.int(len(iv)), (*C.char)(unsafe.Pointer(&data[0])), (*C.int)(unsafe.Pointer(&length)), (*C.int)(unsafe.Pointer(&err)))
// if err is not 0, there is an error
if int(err) != 0 {
return nil, errors.New(C.GoString(C.getError(err)))
}
// ensure that memory is freed on the decrypted data after it is converted to Go bytes
defer C.free(unsafe.Pointer(decryptedData))
// return the Go bytes of the decrypted data
return C.GoBytes(unsafe.Pointer(decryptedData), length), nil
}

153
mcrypt_test.go Normal file
View file

@ -0,0 +1,153 @@
package mcrypt
import (
"bytes"
"crypto/rand"
mrand "math/rand"
"testing"
)
func TestEncrypt(t *testing.T) {
dataSizes := []int{8, 13, 16, 32, 64, 1024, 1048576, 4194304, (mrand.Int() % 26214400) + 1}
for _, dataSize := range dataSizes {
key := make([]byte, 32)
_, err := rand.Read(key)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
iv := make([]byte, 32)
_, err = rand.Read(iv)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
data := make([]byte, dataSize)
_, err = rand.Read(data)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
encrypted, err := Encrypt(key, iv, data)
if err != nil {
t.Error("Failed Encrypt with error: " + err.Error())
}
if bytes.Equal(encrypted, data) {
t.Error("Failed Encrypt: Encrypted data was the same as input data")
}
decrypted, err := Decrypt(key, iv, encrypted)
if err != nil {
t.Error("Failed Decrypt with error: " + err.Error())
}
cryptLen := len(decrypted)
for i := 0; i < cryptLen; i++ {
if i >= dataSize {
if decrypted[i] != 0 {
t.Error("Failed encryption/decryption: invalid padding")
}
} else if decrypted[i] != data[i] {
t.Error("Failed encryption/decryption: invalid data")
}
}
}
key := make([]byte, 31)
iv := make([]byte, 32)
data := make([]byte, 32)
_, err := Encrypt(key, iv, data)
if err == nil {
t.Error("Failed to receive error for invalid key size")
}
key = make([]byte, 32)
iv = make([]byte, 31)
_, err = Encrypt(key, iv, data)
if err == nil {
t.Error("Failed to receive error for invalid iv size")
}
key = make([]byte, 32)
iv = make([]byte, 32)
_, err = Encrypt(key, iv, []byte{})
if err == nil {
t.Error("Failed to receive error for 0 byte data size")
}
}
func TestDecrypt(t *testing.T) {
dataSizes := []int{8, 13, 16, 32, 64, 1024, 1048576, 4194304, (mrand.Int() % 26214400) + 1}
for _, dataSize := range dataSizes {
key := make([]byte, 32)
_, err := rand.Read(key)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
iv := make([]byte, 32)
_, err = rand.Read(iv)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
data := make([]byte, dataSize)
_, err = rand.Read(data)
if err != nil {
t.Error("Failed to get random data from crypto/rand")
}
encrypted, err := Encrypt(key, iv, data)
if err != nil {
t.Error("Failed Encrypt with error: " + err.Error())
}
if bytes.Equal(encrypted, data) {
t.Error("Failed Encrypt: Encrypted data was the same as input data")
}
decrypted, err := Decrypt(key, iv, encrypted)
if err != nil {
t.Error("Failed Decrypt with error: " + err.Error())
}
cryptLen := len(decrypted)
for i := 0; i < cryptLen; i++ {
if i >= dataSize {
if decrypted[i] != 0 {
t.Error("Failed encryption/decryption: invalid padding")
}
} else if decrypted[i] != data[i] {
t.Error("Failed encryption/decryption: invalid data")
}
}
}
key := make([]byte, 31)
iv := make([]byte, 32)
data := make([]byte, 32)
_, err := Decrypt(key, iv, data)
if err == nil {
t.Error("Failed to receive error for invalid key size")
}
key = make([]byte, 32)
iv = make([]byte, 31)
_, err = Decrypt(key, iv, data)
if err == nil {
t.Error("Failed to receive error for invalid iv size")
}
key = make([]byte, 32)
iv = make([]byte, 32)
data = make([]byte, 31)
if err == nil {
t.Error("Failed to receive error for invalid data size")
}
data = make([]byte, 0)
if err == nil {
t.Error("Failed to receive error for 0 byte data size")
}
}