英文:
Making GCM/CBC ciphers streamable in golang
问题
GCM和CBC AES密码在Go语言中无法与StreamWriter或StreamReader一起使用,这迫使我将整个文件分配到内存中。显然,对于大文件来说,这并不理想。
我在考虑通过将一些固定大小的块分配到内存中,并将它们提供给GCM或CBC,使它们可流式化。但我认为这可能是个坏主意,因为设计这种方式肯定有原因。
有人能解释一下为什么这些操作模式不能在不将整个文件分配到内存中的情况下使用吗?
英文:
The GCM and CBC AES ciphers in Go can't be used along side with StreamWriter or StreamReader, which forces me to allocate the entire file into memory. Obviously, this is not ideal with large files.
I was thinking of making them streamable, by allocation some fixed size of blocks into memory, and feeding them to GCM or CBC, but I'm assuming that is probably a bad idea, since there must be a reason they've been designed this way.
Can someone explain why these operation modes can't be used without allocating the entire files into memory?
答案1
得分: 7
简单回答——这是他们设计API的方式。
CBC和GCM是非常不同的模式。GCM是AEAD模式(带有关联数据的认证加密)。你真的需要认证吗?如果不需要,对于大文件来说,CBC是一个很好的选择。你也可以使用CTR或OFB,但它们是流模式,并且在选择IV时非常严格。
在实施任何东西之前,我建议你先了解一下这些模式。你至少要理解它们需要哪些参数,用于什么目的以及如何生成它们。
CBC
BlockMode
接口非常适合加密大文件。你只需要逐块加密。CBC需要填充,但Go没有实现。至少我没有看到。你需要想办法处理这个问题。
GCM
GCM使用AEAD
接口,它只允许你加密和解密整个消息。没有任何理由它应该被实现成这样。GCM是一种流模式,它实际上非常适合流加密。唯一的问题是认证。GCM在最后产生一个标签,它起到了MAC的作用。为了利用这个标签,你不能只是加密一个无限流的数据。你必须将其分成块并分别进行认证。或者做其他事情,但在某个时候你必须读取并验证该标签,否则使用GCM就没有意义。
一些库,包括Go,在加密时会隐式地在末尾附加该标签,并在解密时读取和验证它。个人认为这是一个非常糟糕的设计。标签应该作为一个单独的实体可用,你不能仅仅假设它总是在末尾。而且这不是Go实现中唯一的问题。对此我感到抱歉。最近我遇到了一个特别糟糕的实现。
作为解决方案,我建议你将流分成块,并使用唯一的nonce分别对它们进行加密(这非常重要)。每个块的末尾都会有一个标签,你应该验证它。这样你就可以利用GCM的认证功能。是的,这有点丑陋,但Go不提供对内部方法的访问,因此你无法创建自己的加密API。
作为另一种选择,你可以找到另一个实现。甚至可以是一个C库。我可以建议mbedtls。对我来说,它是我遇到的API方面最好的实现。
英文:
Simple answer - that's how they designed the API.
CBC and GCM are very different modes. GCM is AEAD mode (Authenticated Encryption with Associated Data). Do you actually need authentication? If not, for large files CBC is a good fit. You could also use CTR or OFB but they're streaming modes and are very strict about choosing an IV.
Before implementing anything I suggest you read about these modes. You have to at least understand, which parameters they need, for what purpose and how they should be generated.
CBC
BlockMode
interface is a good fit for encrypting large files. You just need to encrypt block by block. CBC requires padding, but Go doesn't have the implementation. At least, I don't see one. You will have to deal with that somehow.
GCM
GCM uses AEAD
interface, which only allows you to encrypt and decrypt a whole message. There is absolutely no reason why it should be implemented like that. GCM is a streaming mode, it's actually a good fit for streaming encryption. The only problem is authentication. GCM produces a tag at the end, which acts like a MAC. To make use of that tag you can't just encrypt an endless stream of data. You have to split it into chunks and authenticate them separately. Or do something else but at some point you have to read that tag and verify it, otherwise there's no point in using GCM.
What some libraries do, including Go, is they append that tag at the end implicitly on encryption and read and verify it on decryption. Personally, I think that's a very bad design. Tag should be available as a separate entity, you can't just assume that it will always be at the end. And that's not the only one of the problems in Go implementation.
Sorry for that rant. Recently I've dealt with a particulary bad implementation.
As a solution I suggest you split your stream into chunks and encrypt them separately with a unique nonce (that's very important). Each chunk will have a tag at the end, which you should verify. That way you can make use of GCM authentication. Yes, it's kind of ugly but Go doesn't give you access to inner methods, so that you could make your own encryption API.
As an alternative you could find a different implementation. Maybe even a C library. I can suggest mbedtls. For me, it's the best implementation I came across in terms of API.
答案2
得分: 2
这是一个用于从BlockMode中读取的流实现。具体效果可能因情况而异。
type BlockReader struct {
buf []byte
block cipher.BlockMode
in io.Reader
}
func NewBlockReader(blockMode cipher.BlockMode, reader io.Reader) *BlockReader {
return &BlockReader{
block: blockMode,
in: reader,
}
}
func (b *BlockReader) Read(p []byte) (n int, err error) {
toRead := len(p)
mul := toRead / b.block.BlockSize()
size := mul * b.block.BlockSize()
if cap(b.buf) != size {
b.buf = make([]byte, toRead, toRead)
}
read, err := b.in.Read(b.buf)
if err != nil {
return 0, err
}
if read < b.block.BlockSize() {
return 0, io.ErrUnexpectedEOF
}
b.block.CryptBlocks(b.buf, b.buf)
return copy(p, b.buf), nil
}
以上是一个从BlockMode中读取的流实现的代码。你可以根据需要进行调整。
英文:
Heres a stream implementation for reading from a BlockMode. Your mileage may vary.
type BlockReader struct {
buf []byte
block cipher.BlockMode
in io.Reader
}
func NewBlockReader(blockMode cipher.BlockMode,reader io.Reader) *BlockReader {
return &BlockReader{
block: blockMode,
in: reader,
}
}
func (b *BlockReader) Read(p []byte) (n int, err error) {
toRead := len(p)
mul := toRead/b.block.BlockSize()
size := mul*b.block.BlockSize()
if cap(b.buf) != size{
b.buf = make([]byte,toRead,toRead)
}
read, err := b.in.Read(b.buf)
if err != nil {
return 0,err
}
if read < b.block.BlockSize(){
return 0,io.ErrUnexpectedEOF
}
b.block.CryptBlocks(b.buf,b.buf)
return copy(p,b.buf),nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论