123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- module secured.rsa;
- import core.memory;
- import secured.openssl;
- import deimos.openssl.evp;
- import deimos.openssl.rand;
- import deimos.openssl.pem;
- import deimos.openssl.bio;
- import deimos.openssl.rsa;
- import deimos.openssl.engine;
- import secured.random;
- import secured.symmetric;
- import secured.util;
- // ----------------------------------------------------------
- @trusted:
- public class RSA
- {
- private bool _hasPrivateKey;
- public @property bool hasPrivateKey() { return _hasPrivateKey; }
- static EVP_PKEY *keypair;
- public this(const int RSA_KEYLEN = 4096)
- {
- // Reseed the OpenSSL RNG every time we create a new RSA Key to ensure that the result is truely random in threading/forking scenarios.
- ubyte[] seedbuf = random(32);
- RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
- EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, null);
- if (ctx is null) {
- throw new CryptographicException("EVP_PKEY_CTX_new_id failed.");
- }
- scope(exit) {
- if (ctx !is null)
- EVP_PKEY_CTX_free(ctx);
- }
- if(EVP_PKEY_keygen_init(ctx) <= 0) {
- throw new CryptographicException("EVP_PKEY_keygen_init failed.");
- }
- if(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, RSA_KEYLEN) <= 0) {
- throw new CryptographicException("EVP_PKEY_CTX_set_rsa_keygen_bits failed.");
- }
- if(EVP_PKEY_keygen(ctx, &keypair) <= 0) {
- throw new CryptographicException("EVP_PKEY_keygen failed.");
- }
- _hasPrivateKey = true;
- }
- public this(ubyte[] privateKey, ubyte[] password)
- {
- // Reseed the OpenSSL RNG every time we load an existing RSA key to ensure that the result is truely random in threading/forking scenarios.
- ubyte[] seedbuf = random(32);
- RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
- _hasPrivateKey = true;
- ubyte[] pk = cast(ubyte[])privateKey;
- BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
- if (password is null) {
- keypair = PEM_read_bio_PrivateKey(bio, null, null, null);
- } else {
- ubyte[] pwd = cast(ubyte[])password;
- pwd = pwd ~ '\0';
- keypair = PEM_read_bio_PrivateKey(bio, null, null, pwd.ptr);
- }
- BIO_free_all(bio);
- }
- public this(ubyte[] publicKey)
- {
- // Reseed the OpenSSL RNG every time we load an existing RSA key to ensure that the result is truely random in threading/forking scenarios.
- ubyte[] seedbuf = random(32);
- RAND_seed(seedbuf.ptr, cast(int)seedbuf.length);
- _hasPrivateKey = false;
- ubyte[] pk = cast(ubyte[])publicKey;
- BIO* bio = BIO_new_mem_buf(pk.ptr, cast(int)pk.length);
- keypair = PEM_read_bio_PUBKEY(bio, null, null, null);
- BIO_free_all(bio);
- }
- public ~this()
- {
- }
- ubyte[] seal(const ubyte[] plaintext)
- {
- return this.seal(plaintext, SymmetricAlgorithm.AES256_CTR);
- }
- ubyte[] seal(const ubyte[] plaintext, SymmetricAlgorithm algorithm)
- {
- ubyte* _encMsg;
- ubyte* _ek;
- size_t _ekl;
- ubyte* _iv;
- size_t _ivl;
- ubyte** encMsg = &_encMsg;
- ubyte** ek = &_ek;
- size_t* ekl = &_ekl;
- ubyte** iv = &_iv;
- size_t* ivl = &_ivl;
- // The header, symmetric encryption key ek and initialisation vector iv are prefixed the encrypted message
- // Having four length bytes in header imposes a 4 GB limit on the plaintext
- const ubyte* msg = plaintext.ptr;
- size_t msgLen = plaintext.length;
- static if(size_t.sizeof == 8) {
- size_t maxHeaderL = 2 + 2 + 4; // 2 bytes for actual ekl, 2 bytes for actual ivl and 4 bytes for actual length
- size_t maxEKL = EVP_PKEY_get_size(keypair);
- size_t maxIVL = EVP_MAX_IV_LENGTH;
- size_t maxEncMsgLen = msgLen + EVP_MAX_IV_LENGTH;
- size_t maxTotalSize = maxHeaderL + maxEKL + maxIVL + maxEncMsgLen;
- size_t encMsgLen = 0;
- size_t blockLen = 0;
- *ivl = EVP_MAX_IV_LENGTH;
- ubyte* buffer = cast(ubyte*)GC.malloc(maxTotalSize);
- if(buffer == null)
- throw new CryptographicException("Malloc failed.");
- *ek = buffer + maxHeaderL;
- *iv = buffer + maxHeaderL + maxEKL;
- *encMsg = buffer + maxHeaderL + maxEKL + maxIVL;
- version(OpenSSL10) {
- EVP_CIPHER_CTX *rsaEncryptCtx = cast(EVP_CIPHER_CTX*)GC.malloc(EVP_CIPHER_CTX.sizeof);
- if(rsaEncryptCtx == null)
- throw new CryptographicException("Malloc failed.");
- EVP_CIPHER_CTX_init(rsaEncryptCtx);
- } else {
- EVP_CIPHER_CTX *rsaEncryptCtx = EVP_CIPHER_CTX_new();
- }
- scope(exit) {
- if (rsaEncryptCtx !is null) {
- version(OpenSSL10) {
- EVP_CIPHER_CTX_cleanup(rsaEncryptCtx);
- } else {
- EVP_CIPHER_CTX_free(rsaEncryptCtx);
- }
- }
- }
- if(!EVP_SealInit(rsaEncryptCtx, getOpenSslCipher(algorithm), ek, cast(int*)ekl, *iv, &keypair, 1))
- throw new CryptographicException("CEVP_SealInit failed.");
- if(!EVP_SealUpdate(rsaEncryptCtx, *encMsg + encMsgLen, cast(int*)&blockLen, cast(const ubyte*)msg, cast(int)msgLen))
- throw new CryptographicException("EVP_SealUpdate failed.");
- encMsgLen += blockLen;
- if(!EVP_SealFinal(rsaEncryptCtx, *encMsg + encMsgLen, cast(int*)&blockLen))
- throw new CryptographicException("EVP_SealFinal failed.");
- encMsgLen += blockLen;
- buffer[0 .. 2] = (cast(ubyte*)ekl)[0..2];
- buffer[2 .. 4] = (cast(ubyte*)ivl)[0..2];
- ubyte* encMsgLenTemp = cast(ubyte*)(&encMsgLen);
- buffer[4..8] = encMsgLenTemp[0..4];
- assert(*ekl == maxEKL);
- assert(*ivl == maxIVL);
- return buffer[0 .. maxHeaderL + maxEKL + maxIVL + encMsgLen];
- }
- else
- assert(0);
- }
- ubyte[] open(ubyte[] encMessage)
- {
- return this.open(encMessage, SymmetricAlgorithm.AES256_CTR);
- }
- ubyte[] open(ubyte[] encMessage, SymmetricAlgorithm algorithm)
- {
- assert(encMessage.length > 8); // Encrypted message must be larger than header = ekl + ivl + messageLength
- static if(size_t.sizeof == 8) {
- // Header: 2 bytes for actual ekl, 2 bytes for actual ivl and 4 bytes for actual length
- size_t maxHeaderL = 2 + 2 + 4;
- size_t maxEKL = EVP_PKEY_get_size(keypair);
- size_t maxIVL = EVP_MAX_IV_LENGTH;
- ubyte* ek = encMessage.ptr + maxHeaderL;
- ubyte[8] temp = 0;
- temp[0..2] = encMessage[0..2];
- int ekl = (cast(int[])temp)[0];
- ubyte* iv = encMessage.ptr + maxHeaderL + maxEKL;
- temp = 0;
- temp[0..2] = encMessage[2..4];
- size_t ivl = (cast(int[])temp)[0];
- ubyte* encMsg = encMessage.ptr + maxHeaderL + maxEKL + maxIVL;
- temp = 0;
- temp[0..4] = encMessage[4..8];
- size_t encMsgLen = (cast(size_t[])temp)[0];
- size_t decLen = 0;
- size_t blockLen = 0;
- EVP_PKEY *key;
- ubyte* _decMsg;
- auto decMsg = &_decMsg;
- *decMsg = cast(ubyte*)GC.malloc(encMsgLen + ivl);
- if(decMsg == null) {
- throw new CryptographicException("Malloc failed.");
- }
- version(OpenSSL10) {
- EVP_CIPHER_CTX *rsaDecryptCtx = cast(EVP_CIPHER_CTX*)GC.malloc(EVP_CIPHER_CTX.sizeof);
- if(rsaDecryptCtx == null) {
- throw new CryptographicException("Malloc failed.");
- }
- EVP_CIPHER_CTX_init(rsaDecryptCtx);
- } else {
- EVP_CIPHER_CTX *rsaDecryptCtx = EVP_CIPHER_CTX_new();
- }
- scope(exit) {
- if (rsaDecryptCtx !is null) {
- version(OpenSSL10) {
- EVP_CIPHER_CTX_cleanup(rsaDecryptCtx);
- } else {
- EVP_CIPHER_CTX_free(rsaDecryptCtx);
- }
- }
- }
- if(!EVP_OpenInit(rsaDecryptCtx, getOpenSslCipher(algorithm), ek, ekl, iv, keypair))
- throw new CryptographicException("EVP_OpenInit failed.");
- if(!EVP_OpenUpdate(rsaDecryptCtx, cast(ubyte*)*decMsg + decLen, cast(int*)&blockLen, encMsg, cast(int)encMsgLen))
- throw new CryptographicException("EVP_OpenUpdate failed.");
- decLen += blockLen;
- if(!EVP_OpenFinal(rsaDecryptCtx, cast(ubyte*)*decMsg + decLen, cast(int*)&blockLen))
- throw new CryptographicException("EVP_OpenFinal failed.");
- decLen += blockLen;
- return (*decMsg)[0 .. decLen];
- }
- else
- assert(0);
- }
- ubyte[] getPublicKey()
- {
- BIO* bio = BIO_new(BIO_s_mem());
- PEM_write_bio_PUBKEY(bio, keypair);
- ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
- BIO_read(bio, buffer.ptr, cast(int)buffer.length);
- BIO_free_all(bio);
- return buffer;
- }
- public ubyte[] getPrivateKey(string password, int iterations = 25000, bool use3Des = false)
- {
- if (!_hasPrivateKey) {
- return null;
- }
- BIO* bio = BIO_new(BIO_s_mem());
- if (password is null) {
- PEM_write_bio_PKCS8PrivateKey(bio, keypair, null, null, 0, null, null);
- } else {
- ubyte[] pwd = cast(ubyte[])password;
- pwd = pwd ~ '\0';
- PEM_write_bio_PKCS8PrivateKey(
- bio,
- keypair,
- !use3Des ? EVP_aes_256_cbc() : EVP_des_ede3_cbc(),
- null,
- 0,
- null,
- pwd.ptr);
- }
- if(BIO_ctrl_pending(bio) == 0) {
- throw new CryptographicException("No private key written.");
- }
- ubyte[] buffer = new ubyte[BIO_ctrl_pending(bio)];
- BIO_read(bio, buffer.ptr, cast(int)buffer.length);
- BIO_free_all(bio);
- return buffer;
- }
- ubyte[] encrypt(const ubyte[] inMessage)
- in
- {
- import std.exception: enforce;
- enforce(inMessage.length <= (EVP_PKEY_get_size(keypair) - 42), new CryptographicException("Plainttext length exceeds allowance")); // 42 being the padding overhead for OAEP padding using SHA-1
- }
- body
- {
- EVP_PKEY_CTX *ctx;
- ENGINE *eng = null; // Use default RSA implementation
- ubyte *out2;
- const ubyte *in2 = inMessage.ptr;
- size_t outlen;
- size_t inlen = inMessage.length;
- ctx = EVP_PKEY_CTX_new(keypair,eng);
- if (!ctx) {
- throw new CryptographicException("EVP_PKEY_CTX_new.");
- }
- scope(exit) {
- if (ctx !is null) {
- EVP_PKEY_CTX_free(ctx);
- }
- }
- if (EVP_PKEY_encrypt_init(ctx) <= 0) {
- throw new CryptographicException("EVP_PKEY_encrypt_init failed.");
- }
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
- throw new CryptographicException("EVP_PKEY_CTX_set_rsa_padding failed.");
- }
- if (EVP_PKEY_encrypt(ctx, null, &outlen, in2, inlen) <= 0) {
- throw new CryptographicException("EVP_PKEY_encrypt failed.");
- }
- out2 = cast(ubyte*)GC.malloc(outlen);
- if(out2 == null) {
- throw new CryptographicException("Malloc failed.");
- }
- if (EVP_PKEY_encrypt(ctx, out2, &outlen, in2, inlen) <= 0) {
- throw new CryptographicException("EVP_PKEY_encrypt failed.");
- }
- return (out2)[0 .. outlen];
- }
- ubyte[] decrypt(const ubyte[] inMessage)
- in
- {
- assert(inMessage.length == EVP_PKEY_get_size(keypair)); // Should always hold as padding was added during encryption
- }
- body
- {
- EVP_PKEY_CTX *ctx;
- ENGINE *eng = null; // Use default RSA implementation
- ubyte *out2;
- const ubyte *in2 = inMessage.ptr;
- size_t outlen;
- size_t inlen = inMessage.length;
- ctx = EVP_PKEY_CTX_new(keypair,eng);
- if (!ctx) {
- throw new CryptographicException("EVP_PKEY_CTX_new failed");
- }
- scope(exit) {
- if (ctx !is null) {
- EVP_PKEY_CTX_free(ctx);
- }
- }
- if (EVP_PKEY_decrypt_init(ctx) <= 0) {
- throw new CryptographicException("EVP_PKEY_decrypt_init failed.");
- }
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
- throw new CryptographicException("EVP_PKEY_CTX_set_rsa_padding failed.");
- }
- if (EVP_PKEY_decrypt(ctx, null, &outlen, in2, inlen) <= 0) {
- throw new CryptographicException("EVP_PKEY_decrypt failed.");
- }
- out2 = cast(ubyte*)GC.malloc(outlen);
- if(out2 == null) {
- throw new CryptographicException("Malloc failed.");
- }
- if (EVP_PKEY_decrypt(ctx, out2, &outlen, in2, inlen) <= 0) {
- throw new CryptographicException("EVP_PKEY_encrypt failed.");
- }
- return (out2)[0 .. outlen];
- }
- public ubyte[] sign(ubyte[] data, bool useSha256 = false)
- out (signature)
- {
- assert(signature.length == EVP_PKEY_get_size(keypair));
- }
- body
- {
- EVP_MD_CTX *mdctx = null;
- mdctx = EVP_MD_CTX_new();
- if (mdctx is null) {
- throw new CryptographicException("Unable to create the MD signing context.");
- }
- scope(exit) {
- if (mdctx !is null) {
- EVP_MD_CTX_free(mdctx);
- }
- }
- auto alg = (!useSha256 ? EVP_sha384() : EVP_sha256());
- if (EVP_DigestSignInit(mdctx, null, alg, null, keypair) != 1) {
- throw new CryptographicException("Unable to initialize the signing digest.");
- }
- if (EVP_DigestSignUpdate(mdctx, data.ptr, data.length) != 1) {
- throw new CryptographicException("Unable to set sign data.");
- }
- size_t signlen = 0;
- if (EVP_DigestSignFinal(mdctx, null, &signlen) != 1) {
- throw new CryptographicException("Unable to calculate signature length.");
- }
- ubyte[] sign = new ubyte[signlen];
- if (EVP_DigestSignFinal(mdctx, sign.ptr, &signlen) != 1) {
- throw new CryptographicException("Unable to finalize signature");
- }
- return sign[0..signlen];
- }
- public bool verify(ubyte[] data, ubyte[] signature, bool useSha256 = false)
- in
- {
- assert(signature.length == EVP_PKEY_get_size(keypair));
- }
- body
- {
- EVP_MD_CTX *mdctx = null;
- mdctx = EVP_MD_CTX_new();
- if (mdctx is null) {
- throw new CryptographicException("Unable to create the MD signing context.");
- }
- scope(exit) {
- if (mdctx !is null) {
- EVP_MD_CTX_free(mdctx);
- }
- }
- auto alg = (!useSha256 ? EVP_sha384() : EVP_sha256());
- if (EVP_DigestVerifyInit(mdctx, null, alg, null, keypair) != 1) {
- throw new CryptographicException("Unable to initialize the verification digest.");
- }
- if (EVP_DigestVerifyUpdate(mdctx, data.ptr, data.length) != 1) {
- throw new CryptographicException("Unable to set verify data.");
- }
- int ret = EVP_DigestVerifyFinal(mdctx, signature.ptr, signature.length);
- return ret == 1;
- } // verify()
- } // class RSA
- // ----------------------------------------------------------
- // UNITTESTING BELOW
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing seal and open functions:");
- auto keypair = new RSA();
- scope(exit) keypair.destroy();
- ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
- ubyte[] encMessage = keypair.seal(plaintext);
- ubyte[] decMessage = keypair.open(encMessage);
- assert(plaintext.length == decMessage.length);
- assert(plaintext == decMessage);
- }
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing getXxxKey functions and constructors:");
- auto keypairA = new RSA();
- scope(exit) keypairA.destroy();
- auto privateKeyA = keypairA.getPrivateKey(null);
- auto publicKeyA = keypairA.getPublicKey();
- ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
- // Creating key from public key only
- auto keypairB = new RSA(publicKeyA);
- scope(exit) keypairB.destroy();
- auto privateKeyB = keypairB.getPrivateKey(null);
- auto publicKeyB = keypairB.getPublicKey();
- assert(privateKeyA != privateKeyB, "Private keys A and B match - they should NOT do so");
- assert(publicKeyA == publicKeyB, "Public keys A and B does not match");
- // Creating key from private key only
- auto keypairC = new RSA(privateKeyA, null);
- scope(exit) keypairC.destroy();
- auto publicKeyC = keypairC.getPublicKey();
- auto privateKeyC = keypairC.getPrivateKey(null);
- assert(privateKeyA == privateKeyC, "Private keys A and C does not match");
- assert(publicKeyA == publicKeyC, "Public keys A and C does not match");
- }
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing sealing and opening with keys, which have been constructed on getXxxKey output:");
- auto keypairA = new RSA();
- scope(exit) keypairA.destroy();
- auto privateKeyA = keypairA.getPrivateKey(null);
- auto publicKeyA = keypairA.getPublicKey();
- ubyte[] plaintext = cast(ubyte[])"This is a test This is a test This is a test This is a test";
- // Creating key from public key only
- auto keypairB = new RSA(publicKeyA);
- scope(exit) keypairB.destroy();
- auto publicKeyB = keypairB.getPublicKey();
- assert(publicKeyA == publicKeyB, "Public keys A and B does not match");
- // Creating key from private key only
- auto keypairC = new RSA(privateKeyA, null);
- scope(exit) keypairC.destroy();
- auto privateKeyC = keypairC.getPrivateKey(null);
- assert(privateKeyA == privateKeyC, "Private keys A and C does not match");
- // Sealing plaintext using public key
- ubyte[] encMessage = keypairB.seal(plaintext);
- // Opening encrypted message using private key
- ubyte[] decMessage = keypairC.open(encMessage);
- assert(plaintext.length == decMessage.length);
- assert(plaintext == decMessage);
- }
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing RSA only encrypt/decrypt functions:");
- auto keypair = new RSA();
- scope(exit) keypair.destroy();
- ubyte[48] plaintext = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
- ubyte[] encMessage = keypair.encrypt(plaintext);
- ubyte[] decMessage = keypair.decrypt(encMessage);
- assert(plaintext.length == decMessage.length);
- assert(plaintext == decMessage);
- }
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing RSA encrypt/decrypt limit:");
- auto keypair = new RSA(2048); // Only allows for (2048/8)-42 = 214 bytes to be asymmetrically RSA encrypted
- scope(exit) keypair.destroy();
- // This should work
- ubyte[214] plaintext214 = 2; // 2 being an arbitrary value
- ubyte[] encMessage214 = keypair.encrypt(plaintext214);
- assert(encMessage214.length == 2048 / 8);
- ubyte[] decMessage214 = keypair.decrypt(encMessage214);
- assert(plaintext214.length == decMessage214.length);
- assert(plaintext214 == decMessage214);
- // This should NOT work, as the plaintext is larger that allowed for this 2048 bit RSA keypair
- ubyte[215] plaintext215 = 2; // 2 being an arbitrary value
- import std.exception: assertThrown;
- assertThrown!CryptographicException(keypair.encrypt(plaintext215));
- }
- // ----------------------------------------------------------
- unittest
- {
- import std.stdio;
- writeln("Testing RSA Signing/Verification:");
- import std.digest;
- auto keypair = new RSA();
- scope(exit) keypair.destroy();
- ubyte[48] data = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
- ubyte[48] data2 = [ 0x1, 0x2, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
- 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
- ubyte[] sig = keypair.sign(data);
- writeln("Signature: ", toHexString!(LetterCase.lower)(sig));
- assert(keypair.verify(data, sig));
- assert(!keypair.verify(data2, sig));
- }
|