65.9K
CodeProject 正在变化。 阅读更多。
Home

用于 .NET 的基于流的加密

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.10/5 (9投票s)

2007 年 2 月 20 日

CPOL

4分钟阅读

viewsIcon

52611

downloadIcon

566

一种 C# 中的对称流式加密方法,基于滚动密码和 mod-257 乘法

引言

.NET Framework 定义了几种优秀的对称加密算法:DES、Rijndael 等。对于“块工作”(加密文件、文本框内容等),这些算法非常出色。但是,当处理未知长度的流时,例如 TCP 套接字,它们有一个主要的缺点:它们需要多字节块中的数据。如果您需要 8 个字节,而您目前只收到了 6 个字节,您根本无法向数据消费者提供任何信息。

对于这种基于流的环境,一种单独获取每个字节并生成编码/解码字节的算法非常有用。由于字节之间的联系较少,这种方法在密码学上较弱(因此我不建议以这种方式运行您的网上银行!),但对于业余用途,真正需要的是简单地查看传输的数据不会泄露任何信息。

与所有对称算法一样,如果密钥未保持安全,则安全性将完全丧失。您应始终以安全的方式传输重要安全通道的密钥,无论是通过双方通过离线来源知道密钥,还是通过公钥加密交换过程。在使用此加密方法的新版本套接字库(链接待定)中,密钥交换使用 RSA,这在 .NET 中很容易做到。

用途

该算法被编码为一对实现 ICryptoTransform 接口的类。它们位于 RedCorona.Cryptography 命名空间中。一个简单的使用示例是

void EncryptionTest(string text, byte[] key){
 byte[] plain = Encoding.UTF8.GetBytes(text);
 BaseCrypto enc = new SimpleEncryptor(key), dec = new SimpleDecryptor(key);
 byte[] cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
 byte[] plain2 = dec.TransformFinalBlock(cipher, 0, cipher.Length);
 // plain and plain2 should now be byte-for-byte equal
}

请注意,没有内置的检查;如果您尝试解密错误的数据,或者使用错误的密钥解密,您将生成无意义的结果。我建议您在输入中插入某种形式的校验和或识别字节,以便您可以检测到何时解密出现问题。

算法

单个字节的加密利用了 257 是素数的有用事实,因此([1..256]×key) mod 257 对于 [1..256] 中的任何密钥都是一对一的(因此是可逆的)映射。模空间中的乘法是生成良好分散的密码数据的好方法。该算法的最简单版本是

cipher-byte: (((plain-byte+1)×(key-byte+1)) mod 257)-1

我将此操作称为 mul257,因此这个简单的算法将是

cipher-byte: mul257(plain-byte, key-byte)

但是,这很容易受到纯文本在已知位置包含零的攻击,这在计算机数据中非常常见。(零 – 对于乘法变为 1 – 导致密文等于密钥,因此如果已知纯文本零的位置,则可以很容易地确定密钥的部分。)因此,实现的版本是

cipher-byte: mul257(plain-byte+key-byte, key-byte)

现在,当 byte(plain-byte+key-byte) 为零时,密文等于密钥,这在不知道密钥的情况下是无法知道的。解密很简单

plain-byte: mul257(cipher-byte, key-byte')-key-byte

...其中 x'x 的 257 补码,即 xx' mod 257 = 1。(这些数字存储在查找表中。)

显然,单字节密钥很容易通过暴力破解,并且是不够的。因此,我的算法使用“滚动密码”;有点像德国的 Enigma 机器,每次编码新字节时,密钥字节都会更改。您传递给类的“加密密钥”是任意长度的字节向量。每次加密一个新字节后,密钥字节在完成加密后被修改为

new-key-byte: mul257(mul257(key-byte, keyvec[keypos++]), cipher-byte+plain-byte)

使用密码字节比仅仅使用纯字节能更好地分散密钥,但是必须包含纯字节才能使此乘法具有任何安全价值,因为密码字节对于破解者来说是(显然)可用的。密钥向量到达末尾后会回绕。初始密钥字节生成为密钥向量的总和(mod 256),因此,即使密钥的任何字节根本未在加密中使用,更改密钥的任何字节也会产生完全不同的字节流。

由于加密取决于较早的字符,因此此算法不适合在某些数据可能丢失或顺序错误时使用。也就是说,encrypt(plaintext) 的值会因过去传递给 encrypt 的内容而异。

但是,因为它依次处理每个字节,所以它适用于数据可能以不可预测的方式被分解的情况,只要它是按顺序排列的即可。那是

encrypt(a,b),encrypt(c) == encrypt(a),encrypt(b,c) 

代码

由于代码非常小(3 KB),因此这里以易于复制粘贴的形式提供。如果您使用它,请记住注明我的姓名并链接回本文!

// Stream based encryption
// new SimpleEncryptor(key);

// (C) Richard Smith 2005-7
//   bobjanova@gmail.com
// You can use this for free and give it to people as much as you like
// as long as you leave a credit to me :).

using System;
using System.Security.Cryptography;

namespace RedCorona.Cryptography {
  // Cryptographic classes
  public abstract class BaseCrypto : ICryptoTransform {
    public int InputBlockSize { get { return 1; } }
    public int OutputBlockSize { get { return 1; } }
    public bool CanTransformMultipleBlocks { get { return true; } }
    public bool CanReuseTransform { get { return true; } }    
    
    protected byte[] key;
    protected byte currentKey;
    protected int done = 0, keyinx = 0;
    
    protected BaseCrypto(byte[] key){
      if(key.Length == 0) throw new ArgumentException("Must provide a key");
      this.key = key;
      currentKey = 0;
      for(int i = 0; i < key.Length; i++) currentKey += key[i];
    }
    
    protected abstract byte DoByte(byte b);
    public int TransformBlock(byte[] from, int frominx, int len, byte[] to, int toinx){
      for(int i = 0; i < len; i++){      
        to[toinx + i] = DoByte(from[frominx + i]);
        BumpKey();
      }
      return len;
    }
    public byte[] TransformFinalBlock(byte[] from, int frominx, int len){
      byte[] to = new byte[len];
      TransformBlock(from, frominx, len, to, 0);
      return to;
    }
    protected void BumpKey(){
      keyinx = (keyinx + 1) % key.Length;
      currentKey = Multiply257(key[keyinx], currentKey);
    }
    
    protected static byte Multiply257(byte a, byte b){
       return (byte)((((a + 1) * (b + 1)) % 257) - 1);
    }
    
    protected static byte[] complements = {
      0,128,85,192,102,42,146,224,199,179,186,149,177,201,119,240,
      120,99,229,89,48,221,189,74,71,88,237,100,194,59,198,248,
      147,188,234,49,131,114,144,44,162,152,5,110,39,94,174,165,
      20,35,125,172,96,118,242,178,247,225,60,29,58,227,101,252,
      86,73,233,222,148,245,180,24,168,65,23,185,246,200,243,150,
      164,209,95,204,126,2,64,183,25,19,208,175,151,215,45,82,
      52,138,134,17,27,62,4,214,163,176,244,187,223,249,43,217,
      115,123,37,112,133,158,53,14,16,157,139,113,219,50,84,254,
      1,171,205,36,142,116,98,239,241,202,97,122,143,218,132,140,
      38,212,6,32,68,11,79,92,41,251,193,228,238,121,117,203,
      173,210,40,104,80,47,236,230,72,191,253,129,51,160,46,91,
      105,12,55,9,70,232,190,87,231,75,10,107,33,22,182,169,
      3,154,28,197,226,195,30,8,77,13,137,159,83,130,220,235,
      90,81,161,216,145,250,103,93,211,111,141,124,206,21,67,108,
      7,57,196,61,155,18,167,184,181,66,34,207,166,26,156,135,
      15,136,54,78,106,69,76,56,31,109,213,153,63,170,127,255
    };
    protected static byte Complement257(byte b){ return complements[b]; }
    
    public void Dispose(){} // for IDisposable
  }
  
  public class SimpleEncryptor : BaseCrypto {
    public SimpleEncryptor(byte[] key) : base(key) {}
    
    protected override byte DoByte(byte b){
      byte b2 = Multiply257((byte)(b+currentKey), currentKey);
      currentKey = Multiply257((byte)(b+b2), currentKey);
      return b2;
    }
  }
  public class SimpleDecryptor : BaseCrypto {
    public SimpleDecryptor(byte[] key) : base(key) {}
    
    protected override byte DoByte(byte b){
      byte b2 = (byte)(Multiply257(b, Complement257(currentKey)) - currentKey);
      currentKey = Multiply257((byte)(b+b2), currentKey);
      return b2;
    }
  }
}
© . All rights reserved.