首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Salsa20流密码实现

Salsa20流密码实现
EN

Code Review用户
提问于 2015-08-20 03:43:27
回答 3查看 1.9K关注 0票数 8

我已经将Salsa20流密码实现为ICryptoTransform。它运行得相当快,并且成功地加密和解密了我所有的测试。我主要希望对Salsa20算法进行审查(因为C#中很少有引用实现),以确保我没有遗漏任何东西。我还想输入ICryptoTransform接口的实现。

代码语言:javascript
复制
namespace Salsa20Cipher {
public sealed class Salsa20CryptoTransform : ICryptoTransform {
    // The ChaCha20 state 
    private uint[] state;
    private readonly int numRounds;

    // Construct a new Salsa20 state.
    public Salsa20CryptoTransform(byte[] key, byte[] iv) {
        if (key == null) {
            throw new ArgumentNullException("key");
        }
        if (iv == null) {
            throw new ArgumentNullException("iv");
        }
        if (key.Length != 32) {
            throw new ArgumentException(
                "Key length must be 32 bytes. Actual is " + key.Length.ToString()
            );
        }
        if (iv.Length < 8) {
            throw new ArgumentException(
                "Nonce should have 8 bytes. Actual is " + iv.Length.ToString()
            );
        }

        Initialize(key, iv);
        numRounds = 20;
    }



    // Initialize the Salsa state with the given key and nonce. A 32-byte 
    // (256-bit) key is required. The nonce must be at least 8-bytes 
    // (64-bits) long. If it is any larger, only the first 64 bits will be
    // used. 
    private void Initialize(byte[] key, byte[] iv) {
        state = new uint[16];

        state[1] = ToUInt32(key, 0);
        state[2] = ToUInt32(key, 4);
        state[3] = ToUInt32(key, 8);
        state[4] = ToUInt32(key, 12);

        byte[] sigma = Encoding.ASCII.GetBytes("expand 32-byte k");

        byte[] constants = sigma;
        int keyIndex = key.Length - 16;

        state[11] = ToUInt32(key, keyIndex + 0);
        state[12] = ToUInt32(key, keyIndex + 4);
        state[13] = ToUInt32(key, keyIndex + 8);
        state[14] = ToUInt32(key, keyIndex + 12);

        state[0] = ToUInt32(constants, 0);
        state[5] = ToUInt32(constants, 4);
        state[10] = ToUInt32(constants, 8);
        state[15] = ToUInt32(constants, 12);

        state[6] = ToUInt32(iv, 0);
        state[7] = ToUInt32(iv, 4);
        state[8] = 0;
        state[9] = 0;
    }

    // Transforms the specified region of the specified byte array. 
    public byte[] TransformFinalBlock(byte[] inputBuffer, 
        int inputOffset, int inputCount) {

        // No parameter checking needed as that is handled in TransformBlock()
        byte[] output = new byte[inputCount];
        TransformBlock(inputBuffer, inputOffset, inputCount, output, 0);

        return output;
    }

    // Encrypt an arbitrary-length plaintext message (inputBuffer), writing the 
    // resulting ciphertext to the outputBuffer. The number of bytes to read 
    // from the input buffer is determined by inputCount.
    public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, 
        byte[] outputBuffer, int outputOffset) {

        /* Check the parameters */
        if (inputBuffer == null) {
            throw new ArgumentNullException("inputBuffer");
        }
        if (inputOffset < 0 || inputOffset >= inputBuffer.Length) {
            throw new ArgumentOutOfRangeException("inputOffset");
        }
        if (inputCount < 0 || 
            (inputOffset + inputCount) > inputBuffer.Length) {
            throw new ArgumentOutOfRangeException("inputCount");
        }
        if (outputBuffer == null) {
            throw new ArgumentNullException("outputBuffer");
        }
        if (outputOffset < 0 || 
            (outputOffset + inputCount) > outputBuffer.Length) {
            throw new ArgumentOutOfRangeException("outputOffset");
        }
        if (state == null) {
            throw new ObjectDisposedException(GetType().Name);
        }

        byte[] output = new byte[64];
        int bytesTransformed = 0;

        while (inputCount > 0) {
            Salsa20Core(output, state);

            state[8] = AddOne(state[8]);
            if (state[8] == 0) {
                /* Stopping at 2^70 bytes per nonce is the 
                 * user's responsibility 
                 */
                state[9] = AddOne(state[9]);
            }

            int blockSize = Math.Min(64, inputCount);

            for (int i = 0; i < blockSize; i++) {
                outputBuffer[outputOffset + i] = 
                    (byte) (inputBuffer[inputOffset + i] ^ output[i]);
            }

            bytesTransformed += blockSize;

            inputCount -= 64;
            outputOffset += 64;
            inputOffset += 64;
        }

        return bytesTransformed;
    }

    // The Salsa20 Core Function reads a 64-byte vector x and produces a 64-byte 
    // vector Salsa20(x). This is the basis of the Salsa20 Stream Cipher. 
    private void Salsa20Core(byte[] output, uint[] input) {
        uint[] tmp = (uint[]) input.Clone();

        for (int i = numRounds; i > 0; i -= 2) {
            tmp[4] ^= Rotate(Add(tmp[0], tmp[12]), 7);
            tmp[8] ^= Rotate(Add(tmp[4], tmp[0]), 9);
            tmp[12] ^= Rotate(Add(tmp[8], tmp[4]), 13);
            tmp[0] ^= Rotate(Add(tmp[12], tmp[8]), 18);
            tmp[9] ^= Rotate(Add(tmp[5], tmp[1]), 7);
            tmp[13] ^= Rotate(Add(tmp[9], tmp[5]), 9);
            tmp[1] ^= Rotate(Add(tmp[13], tmp[9]), 13);
            tmp[5] ^= Rotate(Add(tmp[1], tmp[13]), 18);
            tmp[14] ^= Rotate(Add(tmp[10], tmp[6]), 7);
            tmp[2] ^= Rotate(Add(tmp[14], tmp[10]), 9);
            tmp[6] ^= Rotate(Add(tmp[2], tmp[14]), 13);
            tmp[10] ^= Rotate(Add(tmp[6], tmp[2]), 18);
            tmp[3] ^= Rotate(Add(tmp[15], tmp[11]), 7);
            tmp[7] ^= Rotate(Add(tmp[3], tmp[15]), 9);
            tmp[11] ^= Rotate(Add(tmp[7], tmp[3]), 13);
            tmp[15] ^= Rotate(Add(tmp[11], tmp[7]), 18);
            tmp[1] ^= Rotate(Add(tmp[0], tmp[3]), 7);
            tmp[2] ^= Rotate(Add(tmp[1], tmp[0]), 9);
            tmp[3] ^= Rotate(Add(tmp[2], tmp[1]), 13);
            tmp[0] ^= Rotate(Add(tmp[3], tmp[2]), 18);
            tmp[6] ^= Rotate(Add(tmp[5], tmp[4]), 7);
            tmp[7] ^= Rotate(Add(tmp[6], tmp[5]), 9);
            tmp[4] ^= Rotate(Add(tmp[7], tmp[6]), 13);
            tmp[5] ^= Rotate(Add(tmp[4], tmp[7]), 18);
            tmp[11] ^= Rotate(Add(tmp[10], tmp[9]), 7);
            tmp[8] ^= Rotate(Add(tmp[11], tmp[10]), 9);
            tmp[9] ^= Rotate(Add(tmp[8], tmp[11]), 13);
            tmp[10] ^= Rotate(Add(tmp[9], tmp[8]), 18);
            tmp[12] ^= Rotate(Add(tmp[15], tmp[14]), 7);
            tmp[13] ^= Rotate(Add(tmp[12], tmp[15]), 9);
            tmp[14] ^= Rotate(Add(tmp[13], tmp[12]), 13);
            tmp[15] ^= Rotate(Add(tmp[14], tmp[13]), 18);
        }

        for (int i = 0; i < 16; i++) {
            ToBytes(Add(tmp[i], input[i]), output, 4 * i);
        }
    }

    /* Bit Twiddling methods */

    // Serialize the input integer into the output buffer. The input integer 
    // will be split into 4 bytes and put into four sequential places in the 
    // output buffer, starting at the outputOffset. 
    private static void ToBytes(uint input, byte[] output, int outputOffset) {
        unchecked {
            output[outputOffset] = (byte) input;
            output[outputOffset + 1] = (byte) (input >> 8);
            output[outputOffset + 2] = (byte) (input >> 16);
            output[outputOffset + 3] = (byte) (input >> 24);
        }
    }

    private static uint Rotate(uint v, int c) {
        return (v << c) | (v >> (32 - c));
    }

    // Unchecked integer addition. The Salsa spec defines certain operations 
    // to use 32-bit unsigned integer addition modulo 2^32. 
    private static uint Add(uint v, uint w) {
        return unchecked(v + w);
    }

    // Add 1 to the input parameter using unchecked integer addition. The 
    // Salsa spec defines certain operations to use 32-bit unsigned integer 
    // addition modulo 2^32. 
    private static uint AddOne(uint v) {
        return unchecked(v + 1);
    }

    // Convert four bytes of the input buffer into an unsigned 
    // 32-bit integer, beginning at the inputOffset.
    private static uint ToUInt32(byte[] input, int inputOffset) {
        unchecked {
            return (uint) (((input[inputOffset] |
                            (input[inputOffset + 1] << 8)) |
                            (input[inputOffset + 2] << 16)) |
                            (input[inputOffset + 3] << 24));
        }
    }

    /* ICryptoTransform Overrides */

    // Clear and dispose of the internal Salsa state. 
    public void Dispose() {
        if (state != null) {
            Array.Clear(state, 0, state.Length);
        }

        state = null;
    }

    // Determine whether the current transform can be reused (Read-Only) 
    public bool CanReuseTransform {
        get {
            return false;
        }
    }

    // Determine whether multiple blocks can be transformed (Read-Only) 
    public bool CanTransformMultipleBlocks {
        get {
            return true;
        }
    }

    // Get the input block size, in bytes (Read-Only) 
    public int InputBlockSize {
        get {
            return 64;
        }
    }

    // Get the output block size, in bytes (Read-Only) 
    public int OutputBlockSize {
        get {
            return 64;
        }
    }
}
}

我应该提到的是,方法注释的IRL要好得多,但我在这里对它们进行了浓缩,以减少行数。

EN

回答 3

Code Review用户

发布于 2015-08-20 11:29:36

这段代码太疯狂了:

代码语言:javascript
复制
        tmp[4] ^= Rotate(Add(tmp[0], tmp[12]), 7);
        tmp[8] ^= Rotate(Add(tmp[4], tmp[0]), 9);
        tmp[12] ^= Rotate(Add(tmp[8], tmp[4]), 13);
        tmp[0] ^= Rotate(Add(tmp[12], tmp[8]), 18);
        tmp[9] ^= Rotate(Add(tmp[5], tmp[1]), 7);
        tmp[13] ^= Rotate(Add(tmp[9], tmp[5]), 9);
        tmp[1] ^= Rotate(Add(tmp[13], tmp[9]), 13);
        tmp[5] ^= Rotate(Add(tmp[1], tmp[13]), 18);
        tmp[14] ^= Rotate(Add(tmp[10], tmp[6]), 7);
        tmp[2] ^= Rotate(Add(tmp[14], tmp[10]), 9);
        tmp[6] ^= Rotate(Add(tmp[2], tmp[14]), 13);
        tmp[10] ^= Rotate(Add(tmp[6], tmp[2]), 18);
        tmp[3] ^= Rotate(Add(tmp[15], tmp[11]), 7);
        tmp[7] ^= Rotate(Add(tmp[3], tmp[15]), 9);
        tmp[11] ^= Rotate(Add(tmp[7], tmp[3]), 13);
        tmp[15] ^= Rotate(Add(tmp[11], tmp[7]), 18);
        tmp[1] ^= Rotate(Add(tmp[0], tmp[3]), 7);
        tmp[2] ^= Rotate(Add(tmp[1], tmp[0]), 9);
        tmp[3] ^= Rotate(Add(tmp[2], tmp[1]), 13);
        tmp[0] ^= Rotate(Add(tmp[3], tmp[2]), 18);
        tmp[6] ^= Rotate(Add(tmp[5], tmp[4]), 7);
        tmp[7] ^= Rotate(Add(tmp[6], tmp[5]), 9);
        tmp[4] ^= Rotate(Add(tmp[7], tmp[6]), 13);
        tmp[5] ^= Rotate(Add(tmp[4], tmp[7]), 18);
        tmp[11] ^= Rotate(Add(tmp[10], tmp[9]), 7);
        tmp[8] ^= Rotate(Add(tmp[11], tmp[10]), 9);
        tmp[9] ^= Rotate(Add(tmp[8], tmp[11]), 13);
        tmp[10] ^= Rotate(Add(tmp[9], tmp[8]), 18);
        tmp[12] ^= Rotate(Add(tmp[15], tmp[14]), 7);
        tmp[13] ^= Rotate(Add(tmp[12], tmp[15]), 9);
        tmp[14] ^= Rotate(Add(tmp[13], tmp[12]), 13);
        tmp[15] ^= Rotate(Add(tmp[14], tmp[13]), 18);

你真的全是用手打出来的吗?工作聪明,不努力。伪码:

代码语言:javascript
复制
for index_quadruple in ( (8, 4, 0, 9), (12, 8, 4, 13) ...) {
     a, b, c, d = index_quadruple
     tmp[a] ^= Rotate(Add(tmp[b], tmp[c]), d);
}

这样做的时候代码要短得多。

票数 3
EN

Code Review用户

发布于 2016-09-08 04:41:35

首先,请注意完整性检查对于大多数密码非常重要,当然对于流密码(如Salsa20 )来说也是必不可少的。Poly1305是与Salsa20配对的典型MAC;实现并不简单,但您可以转换任何公共域C实现的代码。

您需要添加测试向量以确保正确实现该算法。

票数 2
EN

Code Review用户

发布于 2016-09-08 08:17:46

代码语言:javascript
复制
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, 
    byte[] outputBuffer, int outputOffset) {

    ...

    byte[] output = new byte[64];
    int bytesTransformed = 0;

    while (inputCount > 0) {
        Salsa20Core(output, state);

        ...

        int blockSize = Math.Min(64, inputCount);

        for (int i = 0; i < blockSize; i++) {
            outputBuffer[outputOffset + i] = 
                (byte) (inputBuffer[inputOffset + i] ^ output[i]);
        }

        bytesTransformed += blockSize;

        inputCount -= 64;
        outputOffset += 64;
        inputOffset += 64;
    }

    return bytesTransformed;
}

output这个名字是没有用的:我认为它作为keyStream会更好。但这是个小问题。我在这里看到的大问题是

代码语言:javascript
复制
salsa.TransformBlock(inBuf, 0, 32, outBuf, 0);
salsa.TransformBlock(inBuf, 32, 32, outBuf, 32);

不会给出和

代码语言:javascript
复制
salsa.TransformBlock(inBuf, 0, 64, outBuf, 0);

在我看来,这是一种阻碍表演的虫子。我认为您需要在调用TransformBlock之间存储未使用的密钥流。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/101435

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档