# HG changeset patch # User Thomas Dixon # Date 1235090143 18000 # Node ID 528676d20398c910e9cbe2ca7cb41b75df7e58fc # Parent 6428dfd7fedef0f2de1c3606a3183f04dec3a6f7 Implemented ChaCha. Modified Salsa20 to simplify the implementation of ChaCha. diff -r 6428dfd7fede -r 528676d20398 dcrypt/crypto/ciphers/ChaCha.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dcrypt/crypto/ciphers/ChaCha.d Thu Feb 19 19:35:43 2009 -0500 @@ -0,0 +1,196 @@ +/** + * This file is part of the dcrypt project. + * + * Copyright: Copyright (C) dcrypt contributors 2009. All rights reserved. + * License: MIT + * Authors: Thomas Dixon + */ + +module dcrypt.crypto.ciphers.ChaCha; + +import dcrypt.crypto.StreamCipher; +import dcrypt.crypto.ciphers.Salsa20; +import dcrypt.crypto.params.ParametersWithIV; +import dcrypt.misc.ByteConverter; +import dcrypt.misc.Bitwise; + +/** Implementation of ChaCha designed by Daniel J. Bernstein. */ +class ChaCha : Salsa20 { + char[] name() { + return "ChaCha"; + } + + this() { + i0 = 12; + i1 = 13; + } + + protected void keySetup() { + uint offset; + ubyte[] constants; + + state[4] = ByteConverter.LittleEndian.to!(uint)(workingKey[0..4]); + state[5] = ByteConverter.LittleEndian.to!(uint)(workingKey[4..8]); + state[6] = ByteConverter.LittleEndian.to!(uint)(workingKey[8..12]); + state[7] = ByteConverter.LittleEndian.to!(uint)(workingKey[12..16]); + + if (workingKey.length == 32) { + constants = sigma; + offset = 16; + } else + constants = tau; + + state[ 8] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset..offset+4]); + state[ 9] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+4..offset+8]); + state[10] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+8..offset+12]); + state[11] = ByteConverter.LittleEndian.to!(uint)(workingKey[offset+12..offset+16]); + state[ 0] = ByteConverter.LittleEndian.to!(uint)(constants[0..4]); + state[ 1] = ByteConverter.LittleEndian.to!(uint)(constants[4..8]); + state[ 2] = ByteConverter.LittleEndian.to!(uint)(constants[8..12]); + state[ 3] = ByteConverter.LittleEndian.to!(uint)(constants[12..16]); + } + + protected void ivSetup() { + state[12] = state[13] = 0; + state[14] = ByteConverter.LittleEndian.to!(uint)(workingIV[0..4]); + state[15] = ByteConverter.LittleEndian.to!(uint)(workingIV[4..8]); + } + + protected void salsa20WordToByte(uint[] input, ref ubyte[] output) { + uint[] x = new uint[16]; + x[] = input; + + int i; + for (i = 0; i < 4; i++) { + x[ 0] += x[ 4]; x[12] = Bitwise.rotateLeft(x[12]^x[ 0], 16); + x[ 8] += x[12]; x[ 4] = Bitwise.rotateLeft(x[ 4]^x[ 8], 12); + x[ 0] += x[ 4]; x[12] = Bitwise.rotateLeft(x[12]^x[ 0], 8); + x[ 8] += x[12]; x[ 4] = Bitwise.rotateLeft(x[ 4]^x[ 8], 7); + x[ 1] += x[ 5]; x[13] = Bitwise.rotateLeft(x[13]^x[ 1], 16); + x[ 9] += x[13]; x[ 5] = Bitwise.rotateLeft(x[ 5]^x[ 9], 12); + x[ 1] += x[ 5]; x[13] = Bitwise.rotateLeft(x[13]^x[ 1], 8); + x[ 9] += x[13]; x[ 5] = Bitwise.rotateLeft(x[ 5]^x[ 9], 7); + x[ 2] += x[ 6]; x[14] = Bitwise.rotateLeft(x[14]^x[ 2], 16); + x[10] += x[14]; x[ 6] = Bitwise.rotateLeft(x[ 6]^x[10], 12); + x[ 2] += x[ 6]; x[14] = Bitwise.rotateLeft(x[14]^x[ 2], 8); + x[10] += x[14]; x[ 6] = Bitwise.rotateLeft(x[ 6]^x[10], 7); + x[ 3] += x[ 7]; x[15] = Bitwise.rotateLeft(x[15]^x[ 3], 16); + x[11] += x[15]; x[ 7] = Bitwise.rotateLeft(x[ 7]^x[11], 12); + x[ 3] += x[ 7]; x[15] = Bitwise.rotateLeft(x[15]^x[ 3], 8); + x[11] += x[15]; x[ 7] = Bitwise.rotateLeft(x[ 7]^x[11], 7); + x[ 0] += x[ 5]; x[15] = Bitwise.rotateLeft(x[15]^x[ 0], 16); + x[10] += x[15]; x[ 5] = Bitwise.rotateLeft(x[ 5]^x[10], 12); + x[ 0] += x[ 5]; x[15] = Bitwise.rotateLeft(x[15]^x[ 0], 8); + x[10] += x[15]; x[ 5] = Bitwise.rotateLeft(x[ 5]^x[10], 7); + x[ 1] += x[ 6]; x[12] = Bitwise.rotateLeft(x[12]^x[ 1], 16); + x[11] += x[12]; x[ 6] = Bitwise.rotateLeft(x[ 6]^x[11], 12); + x[ 1] += x[ 6]; x[12] = Bitwise.rotateLeft(x[12]^x[ 1], 8); + x[11] += x[12]; x[ 6] = Bitwise.rotateLeft(x[ 6]^x[11], 7); + x[ 2] += x[ 7]; x[13] = Bitwise.rotateLeft(x[13]^x[ 2], 16); + x[ 8] += x[13]; x[ 7] = Bitwise.rotateLeft(x[ 7]^x[ 8], 12); + x[ 2] += x[ 7]; x[13] = Bitwise.rotateLeft(x[13]^x[ 2], 8); + x[ 8] += x[13]; x[ 7] = Bitwise.rotateLeft(x[ 7]^x[ 8], 7); + x[ 3] += x[ 4]; x[14] = Bitwise.rotateLeft(x[14]^x[ 3], 16); + x[ 9] += x[14]; x[ 4] = Bitwise.rotateLeft(x[ 4]^x[ 9], 12); + x[ 3] += x[ 4]; x[14] = Bitwise.rotateLeft(x[14]^x[ 3], 8); + x[ 9] += x[14]; x[ 4] = Bitwise.rotateLeft(x[ 4]^x[ 9], 7); + } + + for (i = 0; i < 16; i++) + x[i] += input[i]; + + int j; + for (i = j = 0; i < x.length; i++,j+=int.sizeof) + output[j..j+int.sizeof] = ByteConverter.LittleEndian.from!(uint)(x[i]); + } + + /** ChaCha test vectors */ + version (UnitTest) { + unittest { + static const char[][] test_keys = [ + "80000000000000000000000000000000", + "0053a6f94c9ff24598eb3e91e4378add", + "00002000000000000000000000000000"~ + "00000000000000000000000000000000", + "0f62b5085bae0154a7fa4da0f34699ec"~ + "3f92e5388bde3184d72a7dd02376c91c" + + ]; + + static const char[][] test_ivs = [ + "0000000000000000", + "0d74db42a91077de", + "0000000000000000", + "288ff65dc42b92f9" + ]; + + static const char[][] test_plaintexts = [ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000", + + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000", + + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000", + + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000"~ + "00000000000000000000000000000000" + + + ]; + + static const char[][] test_ciphertexts = [ + "beb1e81e0f747e43ee51922b3e87fb38"~ + "d0163907b4ed49336032ab78b67c2457"~ + "9fe28f751bd3703e51d876c017faa435"~ + "89e63593e03355a7d57b2366f30047c5", + + "509b267e7266355fa2dc0a25c023fce4"~ + "7922d03dd9275423d7cb7118b2aedf22"~ + "0568854bf47920d6fc0fd10526cfe7f9"~ + "de472835afc73c916b849e91eee1f529", + + "653f4a18e3d27daf51f841a00b6c1a2b"~ + "d2489852d4ae0711e1a4a32ad166fa6f"~ + "881a2843238c7e17786ba5162bc019d5"~ + "73849c167668510ada2f62b4ff31ad04", + + "db165814f66733b7a8e34d1ffc123427"~ + "1256d3bf8d8da2166922e598acac70f4"~ + "12b3fe35a94190ad0ae2e8ec62134819"~ + "ab61addcccfe99d867ca3d73183fa3fd" + ]; + + ChaCha cc = new ChaCha(); + ubyte[] buffer = new ubyte[64]; + char[] result; + for (int i = 0; i < test_keys.length; i++) { + SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(test_keys[i])); + ParametersWithIV params = new ParametersWithIV(key, ByteConverter.hexDecode(test_ivs[i])); + + // Encryption + cc.init(true, params); + cc.update(ByteConverter.hexDecode(test_plaintexts[i]), buffer); + result = ByteConverter.hexEncode(buffer); + assert(result == test_ciphertexts[i], + cc.name()~": ("~result~") != ("~test_ciphertexts[i]~")"); + + // Decryption + cc.init(false, params); + cc.update(ByteConverter.hexDecode(test_ciphertexts[i]), buffer); + result = ByteConverter.hexEncode(buffer); + assert(result == test_plaintexts[i], + cc.name()~": ("~result~") != ("~test_plaintexts[i]~")"); + } + } + } +} diff -r 6428dfd7fede -r 528676d20398 dcrypt/crypto/ciphers/Salsa20.d --- a/dcrypt/crypto/ciphers/Salsa20.d Thu Feb 19 14:45:13 2009 -0500 +++ b/dcrypt/crypto/ciphers/Salsa20.d Thu Feb 19 19:35:43 2009 -0500 @@ -15,10 +15,13 @@ /** Implementation of Salsa20 designed by Daniel J. Bernstein. */ class Salsa20 : StreamCipher { - private { + protected { // Constants - final ubyte[] sigma = cast(ubyte[])"expand 32-byte k", + const ubyte[] sigma = cast(ubyte[])"expand 32-byte k", tau = cast(ubyte[])"expand 16-byte k"; + + // Counter indexes (added for ChaCha) + uint i0, i1; // Internal state uint[] state; @@ -37,6 +40,9 @@ // State expanded into bytes keyStream = new ubyte[64]; + + i0 = 8; + i1 = 9; } void init(bool encrypt, CipherParameters params) { @@ -84,9 +90,9 @@ if (index == 0) { salsa20WordToByte(state, keyStream); - state[8]++; - if (!state[8]) - state[9]++; + state[i0]++; + if (!state[i0]) + state[i1]++; // As in djb's, changing the IV after 2^70 bytes is the user's responsibility // lol glwt } @@ -110,9 +116,9 @@ for (int i = 0; i < input.length; i++) { if (index == 0) { salsa20WordToByte(state, keyStream); - state[8]++; - if (!state[8]) - state[9]++; + state[i0]++; + if (!state[i0]) + state[i1]++; // As in djb's, changing the IV after 2^70 bytes is the user's responsibility // lol glwt } @@ -129,7 +135,7 @@ index = 0; } - private void keySetup() { + protected void keySetup() { uint offset; ubyte[] constants; @@ -154,13 +160,13 @@ state[15] = ByteConverter.LittleEndian.to!(uint)(constants[12..16]); } - private void ivSetup() { + protected void ivSetup() { state[6] = ByteConverter.LittleEndian.to!(uint)(workingIV[0..4]); state[7] = ByteConverter.LittleEndian.to!(uint)(workingIV[4..8]); state[8] = state[9] = 0; } - private void salsa20WordToByte(uint[] input, ref ubyte[] output) { + protected void salsa20WordToByte(uint[] input, ref ubyte[] output) { uint[] x = new uint[16]; x[] = input;