1 module ddata.crypto.encrypt; 2 3 import std.format: format; 4 import deimos.openssl.evp; 5 import ddata.common; 6 import ddata.crypto.algorithm; 7 8 version (OSX) { 9 extern(C) @nogc nothrow private @system { 10 void arc4random_buf(scope void* buf, size_t nbytes); 11 } 12 } 13 14 private void randomFill(ubyte[] buffer) @trusted { 15 // Shamelessly plucked from https://github.com/LightBender/SecureD/ 16 version (OSX) { 17 arc4random_buf(buffer.ptr, buffer.length); 18 } else version (Posix) { 19 import std.stdio: File, _IONBF; 20 try { 21 File urandom = File("/dev/urandom", "rb"); 22 urandom.setvbuf(null, _IONBF); 23 scope(exit) urandom.close(); 24 buffer = urandom.rawRead(buffer); 25 } catch (Exception ex) { 26 throw new Exception("failed to get random bytes - %s".format(ex.msg)); 27 } 28 } else { 29 static assert(0, "Unsupported OS for secure random byte generation"); 30 } 31 } 32 33 /** 34 Encrypts a string using a key with the specified algorithm 35 36 See_Also: 37 `ddata.crypto.decrypt` 38 */ 39 public ubyte[] encrypt(const string data, const string key, Algorithm algorithm = Algorithm.aes128) @safe { 40 final switch (algorithm) { 41 case Algorithm.aes128: 42 return encrypt(cast(const ubyte[])data, cast(const ubyte[])key, () @trusted { return EVP_aes_128_cbc(); }() ); 43 } 44 } 45 46 private ubyte[] encrypt(const ubyte[] data, const ubyte[] key, const(EVP_CIPHER)* cipher) @trusted { 47 auto ctx = EVP_CIPHER_CTX_new(); 48 if (ctx is null) { 49 throw new Exception("Failed to create EVP cipher context - %s".format(getLastError)); 50 } 51 scope(exit) EVP_CIPHER_CTX_free(ctx); 52 53 static ubyte[16] iv16; 54 55 ubyte[] iv = void; 56 int cipherBlockSize = EVP_CIPHER_block_size(cipher); 57 switch (cipherBlockSize) { 58 case 16: 59 iv = iv16[]; 60 break; 61 default: 62 throw new Exception("Cannot handle cipher block size of %s".format(cipherBlockSize)); 63 } 64 65 randomFill(iv); 66 67 EVP_EncryptInit(ctx, cipher, key.ptr, cast(const(ubyte)*)iv.ptr); 68 69 const bufferSize = data.length + cipherBlockSize; 70 ubyte[] buffer = new ubyte[bufferSize]; 71 72 int updateLength = void; 73 EVP_EncryptUpdate(ctx, buffer.ptr, &updateLength, data.ptr, cast(int)data.length); 74 75 int finalLength = void; 76 EVP_EncryptFinal(ctx, &buffer.ptr[updateLength], &finalLength); 77 78 auto ret = iv ~ buffer[0 .. updateLength + finalLength]; 79 return ret; 80 } 81 82 @("should encrypt and decrypt to same message") 83 @safe unittest { 84 import ddata.crypto: decrypt; 85 string password = "some random password"; 86 string message = "this is a call to all you people how is this even happening"; 87 auto encryptedMessage = encrypt(message, password); 88 auto decryptedMessage = decrypt(encryptedMessage, password); 89 assert(message == decryptedMessage); 90 } 91 92 @("should test all algorithms for multiple text and password sizes") 93 @safe unittest { 94 import std.range: generate, take, array; 95 import std.random: uniform; 96 import ddata.crypto: decrypt; 97 98 alias genChars = generate!(() => cast(char)uniform(32, 127)); 99 100 int maxMessageLength = 2000; 101 int maxPasswordLength = 200; 102 int numSteps = 10; 103 foreach (m; 0 .. maxMessageLength / numSteps) { 104 foreach (p; 0 .. maxPasswordLength / numSteps) { 105 int msize = (m + 1) * numSteps; 106 int psize = (p + 1) * numSteps; 107 108 string message = genChars.take(msize).array.idup; 109 string password = genChars.take(psize).array.idup; 110 111 auto encryptedMessage = encrypt(message, password); 112 auto decryptedMessage = decrypt(encryptedMessage, password); 113 assert(message == decryptedMessage); 114 } 115 } 116 }