Files

412 lines
13 KiB
D
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module crypto.threefish512_ctr.main;
import std.random : Random, unpredictableSeed, uniform;
import std.algorithm : min;
import std.conv : emplace;
// import dmd.common.blake3;
import std.array : appender;
import std.exception : enforce;
/* honestly dk if this is cryptographically "secure", as i took most of the implmentation from some russian dub package
i then implemented pkcs5 back then as thats all i knew
then figured just that is not enough and i only had raw stream ciphers so far
so i tried to research how to implement ctr and shit was too complicated so i vibecoded it (probably some flaws and inefficiet, but should be good enough)
and added blake3-mac as without it the implementation would be vulnerable to
replay, paddingfree oracles, bitflip and plaintext-recovery attacks */
// anyways, from what i read forging a blake3 mac or breaking threefish512 in itself
// is beyond fantasy rn so anyone who wants to use this should be fine
// memcpy
extern(C) nothrow @nogc void* memcpy(void* dst, const void* src, size_t n);
extern(C)
{
struct blake3_hasher {
ubyte[256] opaque;
}
void blake3_hasher_init_keyed(blake3_hasher* hasher, const ubyte* key);
void blake3_hasher_update(blake3_hasher* hasher, const ubyte* input, size_t len);
void blake3_hasher_finalize(blake3_hasher* hasher, ubyte* _out, size_t out_len);
}
class Threefish512
{
private:
enum BLOCK_SIZE = 64;
enum Nw = 8;
enum Nr = 72;
enum Ns = Nr / 4;
uint[8] p = [2, 1, 4, 7, 6, 5, 0, 3];
uint[8] p_1 = [6, 1, 0, 7, 2, 5, 4, 3];
uint[4][8] r = [
[46, 36, 19, 37],
[33, 27, 14, 42],
[17, 49, 36, 39],
[44, 9 , 54, 56],
[39, 30, 34, 24],
[13, 50, 10, 17],
[25, 29, 39, 43],
[8 , 35, 56, 22]
];
ulong[3] t;
ulong[8][Ns + 1] subKeys;
alias _mod8 = (ulong a) => a & 7UL;
private void _mix(ref ulong[2] x, ulong r, ref ulong[2] y)
{
y[0] = x[0] + x[1];
y[1] = (x[1] << r) | (x[1] >> (64 - r));
y[1] ^= y[0];
}
private void _demix(ref ulong[2] y, ulong r, ref ulong[2] x)
{
y[1] ^= y[0];
x[1] = (y[1] << (64 - r)) | (y[1] >> r);
x[0] = y[0] - x[1];
}
private void _setup(ulong* keyData, ulong* tweakData) @system
{
ulong[8] K;
ulong[2] T;
ulong[9] key;
ulong kNw = 0x1BD11BDAA9FC1A22;
memcpy(&K[0], keyData, 64);
memcpy(&T[0], tweakData, 16);
foreach (i; 0 .. Nw)
{
kNw ^= K[i];
key[i] = K[i];
}
key[8] = kNw;
t[0] = T[0];
t[1] = T[1];
t[2] = T[0] ^ T[1];
foreach (round; 0 .. Ns + 1)
{
foreach (i; 0 .. Nw)
{
subKeys[round][i] = key[(round + i) % (Nw + 1)];
if (i == Nw - 3) subKeys[round][i] += t[round % 3];
else if (i == Nw - 2) subKeys[round][i] += t[(round + 1) % 3];
else if (i == Nw - 1) subKeys[round][i] += round;
}
}
}
private void _blockEncrypt(ulong* plainData, ulong* c)
{
ulong[8] f;
ulong[8] e;
ulong[2] x, y;
ulong[8] v;
memcpy(&v[0], plainData, 64);
foreach (round; 0 .. Nr)
{
uint s = round >> 2;
foreach (i; 0 .. Nw)
e[i] = (round % 4 == 0) ? v[i] + subKeys[s][i] : v[i];
foreach (i; 0 .. Nw/2)
{
x[0] = e[i*2]; x[1] = e[i*2+1];
_mix(x, r[_mod8(round)][i], y);
f[i*2] = y[0]; f[i*2+1] = y[1];
}
foreach (i; 0 .. Nw) v[i] = f[p[i]];
}
foreach (i; 0 .. Nw) c[i] = v[i] + subKeys[Ns][i];
}
public static ulong[8] generateKey()
{
ulong[8] key;
auto rng = Random(unpredictableSeed);
foreach (i; 0 .. 8) key[i] = uniform!ulong(rng);
return key;
}
public static ulong generateNonce()
{
auto rng = Random(unpredictableSeed);
return uniform!ulong(rng);
}
public static ubyte[32] generateMacKey() {
auto rng = Random(unpredictableSeed);
ubyte[32] key;
foreach(i; 0 .. 4) {
ulong part = uniform!ulong(rng);
foreach(b; 0 .. 8)
key[i*8 + b] = cast(ubyte)((part >> (8*b)) & 0xFF);
}
return key;
}
public void encryptCTR(ulong* plainData, ulong* ciphertext, size_t lengthBytes, ulong[8] key, ulong nonce)
{
ulong[2] tweak = [nonce, 0];
//_setup(key, [nonce, 0]);
_setup(key.ptr, tweak.ptr);
ulong[8] zeroBlock = 0;
size_t numBlocks = (lengthBytes + 63) / 64;
ulong counter = 0;
foreach (blockIndex; 0 .. numBlocks)
{
t[0] = nonce;
t[1] = counter;
t[2] = t[0] ^ t[1];
ulong[8] keystream;
_blockEncrypt(zeroBlock.ptr, keystream.ptr);
size_t start = blockIndex * 8;
size_t blockULongs = ((lengthBytes - start + 7)/8).min(8);
foreach (i; 0 .. blockULongs)
ciphertext[start + i] = plainData[start + i] ^ keystream[i];
counter++;
}
}
private bool ctEqual(ubyte[32] a, ubyte[32] b)
{
ubyte diff = 0;
foreach(i; 0..32)
diff |= a[i] ^ b[i];
return diff == 0;
}
public ubyte[32] gen_tag(ubyte[32] key_mac, ubyte[] nonce, ubyte[] ciphertext)
{
auto data = appender!(ubyte[])();
data.put(nonce);
data.put(ciphertext);
ubyte[32] comp_tag = blake3_mac(key_mac, data.data);
return comp_tag;
}
private bool verify_tag(ubyte[32] key_mac, ubyte[] nonce, ubyte[] ciphertext, ubyte[32] exp_tag)
{
ubyte[32] comp_tag = gen_tag(key_mac, nonce, ciphertext);
return ctEqual(comp_tag, exp_tag);
}
private ubyte[32] blake3_mac(ubyte[32] key_mac, ubyte[] data)
{
blake3_hasher hasher;
blake3_hasher_init_keyed(&hasher, key_mac.ptr);
blake3_hasher_update(&hasher, data.ptr, data.length);
ubyte[32] oout;
blake3_hasher_finalize(&hasher, oout.ptr, 32);
return oout;
}
public void decryptCTR(ulong* ciphertext, ulong* plainData, size_t lengthBytes, ulong[8] key, ulong nonce, ubyte[32] key_mac, ubyte[32] exp_tag)
{
ubyte[8] nbytes;
foreach(i; 0 .. 8) nbytes[i] = cast(ubyte)((nonce >> (8*i)) & 0xFF);
ubyte[] ct_bytes = cast(ubyte[])(cast(ubyte*)ciphertext)[0 .. lengthBytes];
if (verify_tag(key_mac, nbytes[], ct_bytes, exp_tag)) {
encryptCTR(ciphertext, plainData, lengthBytes, key, nonce);
}
}
public void encryptBytes(ubyte[] plaintext, out ubyte[] ciphertext, out ubyte[32] tag, out ulong nonce, out ulong[8] key_enc, out ubyte[32] key_mac)
{
ciphertext.length = plaintext.length;
size_t nLongs = (plaintext.length + 7)/8;
ulong[] plainLongs;
plainLongs.length = nLongs;
foreach(i; 0 .. nLongs) {
ulong val = 0;
foreach(b; 0 .. 8) {
size_t idx = i*8 + b;
if(idx < plaintext.length)
val |= cast(ulong)plaintext[idx] << (8*b);
}
plainLongs[i] = val;
}
ulong[] cipherLongs;
cipherLongs.length = nLongs;
encryptCTR(plainLongs.ptr, cipherLongs.ptr, plaintext.length, key_enc, nonce);
foreach(i; 0 .. nLongs) {
foreach(b; 0 .. 8) {
size_t idx = i*8 + b;
if(idx < plaintext.length)
ciphertext[idx] = cast(ubyte)((cipherLongs[i] >> (8*b)) & 0xFF);
}
}
auto data = appender!(ubyte[])();
foreach(b; 0 .. 8)
data.put(cast(ubyte)((nonce >> (8*b)) & 0xFF));
data.put(ciphertext);
tag = blake3_mac(key_mac, data.data);
}
public ubyte[] decryptBytes(ubyte[] ciphertext, ubyte[32] expected_tag, ubyte[32] key_mac, ulong[8] key_enc, ulong nonce)
{
auto data = appender!(ubyte[])();
foreach(b; 0 .. 8)
data.put(cast(ubyte)((nonce >> (8*b)) & 0xFF));
data.put(ciphertext);
enforce(ctEqual(blake3_mac(key_mac, data.data), expected_tag), "MAC verification failed");
size_t nLongs = (ciphertext.length + 7)/8;
ulong[] cipherLongs;
cipherLongs.length = nLongs;
foreach(i; 0 .. nLongs) {
ulong val = 0;
foreach(b; 0 .. 8) {
size_t idx = i*8 + b;
if(idx < ciphertext.length)
val |= cast(ulong)ciphertext[idx] << (8*b);
}
cipherLongs[i] = val;
}
ulong[] plainLongs;
plainLongs.length = nLongs;
encryptCTR(cipherLongs.ptr, plainLongs.ptr, ciphertext.length, key_enc, nonce);
ubyte[] plaintext;
plaintext.length = ciphertext.length;
foreach(i; 0 .. nLongs) {
foreach(b; 0 .. 8) {
size_t idx = i*8 + b;
if(idx < ciphertext.length)
plaintext[idx] = cast(ubyte)((plainLongs[i] >> (8*b)) & 0xFF);
}
}
return plaintext;
}
}
__gshared Threefish512 tfInstance = new Threefish512();
// Key / MAC / Nonce wrappers
extern(C) export void generateKeyC(ulong* c_out) {
auto key = Threefish512.generateKey(); // returns ulong[8]
for(size_t i = 0; i < 8; i++)
c_out[i] = key[i];
}
extern(C) export ulong generateNonceC() {
return Threefish512.generateNonce();
}
extern(C) export void generateMacKeyC(ubyte* c_out) {
auto key = Threefish512.generateMacKey(); // returns ubyte[32]
for(size_t i = 0; i < 32; i++)
c_out[i] = key[i];
}
// CTR encryption / decryption wrappers
extern(C) export void encryptCTR_C(ulong* plainData, ulong* ciphertext, size_t lengthBytes,
ulong* key_enc, ulong nonce) {
ulong[8] k;
for(size_t i = 0; i < 8; i++) k[i] = key_enc[i];
tfInstance.encryptCTR(plainData, ciphertext, lengthBytes, k, nonce);
}
extern(C) export void decryptCTR_C(ulong* ciphertext, ulong* plainData, size_t lengthBytes,
ulong* key_enc, ulong nonce,
ubyte* key_mac, ubyte* exp_tag) {
ulong[8] k; ubyte[32] mac; ubyte[32] tag;
for(size_t i = 0; i < 8; i++) k[i] = key_enc[i];
for(size_t i = 0; i < 32; i++) { mac[i] = key_mac[i]; tag[i] = exp_tag[i]; }
tfInstance.decryptCTR(ciphertext, plainData, lengthBytes, k, nonce, mac, tag);
}
// encryptBytes / decryptBytes wrappers
extern(C) export void encryptBytes_C(ubyte* plaintext, size_t plainLen,
ubyte* ciphertext, ubyte* tag,
ulong* nonceOut, ulong* key_encOut, ubyte* key_macOut) {
ulong[8] key_enc = Threefish512.generateKey();
ulong nonce = Threefish512.generateNonce();
ubyte[32] key_mac = Threefish512.generateMacKey();
ubyte[] pt; pt.length = plainLen;
for(size_t i = 0; i < plainLen; i++) pt[i] = plaintext[i];
ubyte[] ct; ct.length = plainLen;
ubyte[32] tag_arr;
tfInstance.encryptBytes(pt, ct, tag_arr, nonce, key_enc, key_mac);
for(size_t i = 0; i < 32; i++) tag[i] = tag_arr[i];
for(size_t i = 0; i < plainLen; i++) ciphertext[i] = ct[i];
for(size_t i = 0; i < 8; i++) key_encOut[i] = key_enc[i];
for(size_t i = 0; i < 32; i++) key_macOut[i] = key_mac[i];
*nonceOut = nonce;
}
extern(C) export void decryptBytes_C(ubyte* ciphertext, size_t cipherLen,
ubyte* plaintextOut,
ubyte* expectedTag, ubyte* key_mac,
ulong* key_enc, ulong nonce) {
ulong[8] k; ubyte[32] mac; ubyte[32] tag;
for(size_t i = 0; i < 8; i++) k[i] = key_enc[i];
for(size_t i = 0; i < 32; i++) { mac[i] = key_mac[i]; tag[i] = expectedTag[i]; }
ubyte[] ct; ct.length = cipherLen;
for(size_t i = 0; i < cipherLen; i++) ct[i] = ciphertext[i];
ubyte[] pt = tfInstance.decryptBytes(ct, tag, mac, k, nonce);
for(size_t i = 0; i < cipherLen; i++) plaintextOut[i] = pt[i];
}
// gen_tag wrapper
extern(C) export void gen_tag_C(ubyte* key_mac, ubyte* nonce, size_t nonceLen,
ubyte* ciphertext, size_t cipherLen, ubyte* outTag) {
ubyte[32] k;
for(size_t i = 0; i < 32; i++) k[i] = key_mac[i];
ubyte[] n; n.length = nonceLen;
for(size_t i = 0; i < nonceLen; i++) n[i] = nonce[i];
ubyte[] ct; ct.length = cipherLen;
for(size_t i = 0; i < cipherLen; i++) ct[i] = ciphertext[i];
ubyte[32] tag = tfInstance.gen_tag(k, n, ct);
for(size_t i = 0; i < 32; i++) outTag[i] = tag[i];
}
extern(C) export void threefish512_block_encrypt_C(
ulong* key, ulong* tweak, ulong* plaintext, ulong* ciphertext)
{
ulong[8] k;
ulong[2] t;
for(size_t i=0;i<8;i++) k[i] = key[i];
for(size_t i=0;i<2;i++) t[i] = tweak[i];
tfInstance._setup(k.ptr, t.ptr);
tfInstance._blockEncrypt(plaintext, ciphertext);
}