关联数据的认证加密Aead(authenticated encryption with associated data),是一种同时具备保密性,完整性和可认证性的加密形式,加密过程采用数据分组形式,对同一个密钥,每次加密需要使用不重复的Nonce(Number used only Once),加密后生成验证数据标签(Tag)用于解密时验证,并且可以附加一段明文数据(associated data)作为额外验证数据。
Aead加密模式的这些特征,非常适合用于随机读写文件的加密,待加密文件数据按固定大小分组,分组序号+随机初始化向量IV作为每个分组加密的Nonce,分组序号作为附加验证数据(associated data),加密文件中只需要保存块大小(BlockSize), 随机初始化向量(IV),加密数据块(Block Cipher)即可。
在块大小和初始化向量已知的情况下,任何文件位置的数据分组序号都能被计算出来,而每次加密最小单元是一个数据块,所以可以定位文件的任何位置进行读写,即文件的随机读写。
使用流读写方法实现AeadStream,写入数据:
public override void Write(byte[] src, int offset, int count) { actionPos = streamPos; var actionLen = streamLen; initBuff(count); int writeLen; while (count > 0) { writeLen = count.min(blockSize - blockOff); if (blockOff == 0 && (writeLen == blockSize // overwrite whole block || writeLen >= actionLen - actionPos)) // overwrite exist block { encryptPack(src, offset, writeLen); } else { var dataLen = readFile(pack, pack.Length, blockIdx) - tagSize; if (dataLen != (actionLen - blockIdx * blockSize).min(blockSize)) throw new Error(this, "DataError", dataLen, actionLen - blockIdx * blockSize); packEnc.decrypt(pack, 0, dataLen + tagSize, blockIdx, block); Buffer.BlockCopy(src, offset, block, blockOff, writeLen); encryptPack(block, 0, dataLen.max(blockOff + writeLen)); } offset += writeLen; count -= writeLen; actionPos += writeLen; actionLen = actionLen.max(actionPos); } writeBuff(); streamPos = actionPos; streamLen = actionLen; }
读取数据:
public override int Read(byte[] dst, int offset, int count) { actionPos = streamPos; readBuff(count); int remain = count, dataLen, readLen; while (remain > 0) { if (actionPos >= streamLen) break; dataLen = seekPack() - tagSize; readLen = remain.min(dataLen - blockOff); if (readLen <= 0) throw new Error(this, "DataShort", remain, dataLen, blockOff); if (blockOff == 0 && dataLen <= remain) decryptPack(dataLen, dst, offset); else { decryptPack(dataLen, block); Buffer.BlockCopy(block, blockOff, dst, offset, readLen); } offset += readLen; remain -= readLen; actionPos += readLen; } streamPos = actionPos; return count - remain; }
定位文件位置:
long streamPos = 0; public override long Position { get => streamPos; set { if (value < 0 || value > Length) throw new IOException("pos out of range!"); streamPos = value; } } public override long Seek(long offset, SeekOrigin origin) { var pos = streamPos; switch (origin) { case SeekOrigin.Begin: pos = offset; break; case SeekOrigin.Current: pos += offset; break; case SeekOrigin.End: pos = streamLen - offset; break; } return Position = pos; }
AeadStream使用方法跟FileStream类似,都是Stream的标准读写方法。
完整代码:AeadStream.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using util.crypt; using util.ext; namespace util.rep.aead { public class AeadStream : Stream { public const string Type = "aead"; public const short Version = 1; public static byte[] KeyDomain = "file-key".utf8(); public Stream fs; public AeadConf conf; public static long dataSize(long fileSize, AeadConf conf) { var packTotal = fileSize - headSize(conf); return (packTotal / conf.packSize()) * conf.BlockSize + ((packTotal % conf.packSize()) - conf.tagSize()).max(0); } public static int headSize(AeadConf conf) => Type.Length + 2 + conf.FileIdSize + conf.nonceSize(); byte[] fileId; byte[] nonce; public AeadStream create() { fileId = conf.FileIdSize.aesRnd(); nonce = aeadEnc.NonceSize.aesRnd(); var header = Type.utf8().merge(Version.bytes(), fileId, nonce); fs.write(header); return this; } public AeadStream open() { var header = new byte[prefix]; if (fs.readFull(header) != header.Length || header.utf8(0, Type.Length) != Type) throw new Error(this, "InvalidType"); var ver = header.i16(Type.Length); if (ver > Version) throw new Error(this, "InvalidVersion", ver); fileId = header.sub(Type.Length + 2, conf.FileIdSize); nonce = header.tail(aeadEnc.NonceSize); streamLen = dataSize(fs.Length, conf); return this; } int? pre; int prefix => (int)(pre ?? (pre = headSize(conf))); int packSize => blockSize + tagSize; int blockSize => conf.BlockSize; int tagSize => aeadEnc.TagSize; AeadCrypt ae; AeadCrypt aeadEnc => ae ?? (ae = conf.newCrypt()); PackCrypt pke; PackCrypt packEnc => pke ?? (pke = new PackCrypt(aeadEnc.setKey(conf.deriveKey(KeyDomain.merge(fileId), aeadEnc.KeySize)), nonce)); long actionPos; byte[] pk; byte[] pack => pk ?? (pk = new byte[packSize]); byte[] blk; byte[] block => blk ?? (blk = new byte[blockSize]); long blockIdx => actionPos / blockSize; int blockOff => (int)(actionPos % blockSize); byte[] buff; int buffLen; int buffPos; long buffIdx; void initBuff(int dataLen) { buffLen = ((blockOff + dataLen - 1) / blockSize + 1) * packSize; if (buff == null || buff.Length < buffLen) buff = new byte[buffLen]; buffPos = 0; buffIdx = blockIdx; } void readBuff(int dataLen) { initBuff(dataLen); buffLen = readFile(buff, buffLen, buffIdx); } void writeBuff() { if (buffPos <= 0) return; seekFile(buffIdx); fs.Write(buff, 0, buffPos); } void seekFile(long packIdx) => fs.Position = prefix + packIdx * packSize; int readFile(byte[] dst, int dstLen, long packIdx) { seekFile(packIdx); return fs.readFull(dst, 0, dstLen); } int seekPack() { buffPos = (int)((blockIdx - buffIdx) * packSize); return packSize.min(buffLen - buffPos); } void decryptPack(int dataSize, byte[] dst, int dstOff = 0) => packEnc.decrypt(buff, buffPos, dataSize + tagSize, blockIdx, dst, dstOff); void encryptPack(byte[] src, int srcOff, int srcLen) { packEnc.encrypt(src, srcOff, srcLen, blockIdx, buff, buffPos); buffPos += srcLen + tagSize; } public override int Read(byte[] dst, int offset, int count) { actionPos = streamPos; readBuff(count); int remain = count, dataLen, readLen; while (remain > 0) { if (actionPos >= streamLen) break; dataLen = seekPack() - tagSize; readLen = remain.min(dataLen - blockOff); if (readLen <= 0) throw new Error(this, "DataShort", remain, dataLen, blockOff); if (blockOff == 0 && dataLen <= remain) decryptPack(dataLen, dst, offset); else { decryptPack(dataLen, block); Buffer.BlockCopy(block, blockOff, dst, offset, readLen); } offset += readLen; remain -= readLen; actionPos += readLen; } streamPos = actionPos; return count - remain; } public override void Write(byte[] src, int offset, int count) { actionPos = streamPos; var actionLen = streamLen; initBuff(count); int writeLen; while (count > 0) { writeLen = count.min(blockSize - blockOff); if (blockOff == 0 && (writeLen == blockSize // overwrite whole block || writeLen >= actionLen - actionPos)) // overwrite exist block { encryptPack(src, offset, writeLen); } else { var dataLen = readFile(pack, pack.Length, blockIdx) - tagSize; if (dataLen != (actionLen - blockIdx * blockSize).min(blockSize)) throw new Error(this, "DataError", dataLen, actionLen - blockIdx * blockSize); packEnc.decrypt(pack, 0, dataLen + tagSize, blockIdx, block); Buffer.BlockCopy(src, offset, block, blockOff, writeLen); encryptPack(block, 0, dataLen.max(blockOff + writeLen)); } offset += writeLen; count -= writeLen; actionPos += writeLen; actionLen = actionLen.max(actionPos); } writeBuff(); streamPos = actionPos; streamLen = actionLen; } public override long Seek(long offset, SeekOrigin origin) { var pos = streamPos; switch (origin) { case SeekOrigin.Begin: pos = offset; break; case SeekOrigin.Current: pos += offset; break; case SeekOrigin.End: pos = streamLen - offset; break; } return Position = pos; } public override void SetLength(long value) { throw new NotSupportedException(); } public override bool CanRead => fs.CanRead; public override bool CanSeek => fs.CanSeek; public override bool CanWrite => fs.CanWrite; long streamLen = 0; public override long Length => streamLen; long streamPos = 0; public override long Position { get => streamPos; set { if (value < 0 || value > Length) throw new IOException("pos out of range!"); streamPos = value; } } public override void Flush() => fs.Flush(); public override void Close() { fs?.Close(); fs = null; } } }View Code
Github链接:
https://github.com/bsmith-zhao/vfs/blob/main/util/rep/aead/AeadStream.cs
其他参考:
https://github.com/bsmith-zhao/vfs/blob/main/util/rep/aead/AeadConf.cs
https://github.com/bsmith-zhao/vfs/tree/main/util/crypt
https://github.com/bsmith-zhao/vfs/tree/main/util/crypt/sodium
标签:加密,int,读写,writeLen,dataLen,conf,Aead,byte,public From: https://www.cnblogs.com/bsmith/p/17766307.html