ggstd/crypto/cipher/
cbc.rs

1// Copyright 2023 The rust-ggstd authors. All rights reserved.
2// Copyright 2009 The Go Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6//! Cipher block chaining (CBC) mode.
7//!
8//! CBC provides confidentiality by xoring (chaining) each plaintext block
9//! with the previous ciphertext block before applying the block cipher.
10//!
11//! See NIST SP 800-38A, pp 10-11
12
13use crate::compat;
14use crate::crypto::cipher::{Block, BlockMode};
15use crate::crypto::subtle;
16
17/// CBCEncrypter implements encryption in cipher block chaining
18/// mode, using the given Block.
19pub struct CBCEncrypter<'a, B: Block> {
20    b: &'a B,
21    block_size: usize,
22    iv: Vec<u8>,
23    tmp: Vec<u8>,
24}
25
26impl<'a, B: Block> CBCEncrypter<'a, B> {
27    /// new returns a BlockMode which encrypts in cipher block chaining
28    /// mode, using the given Block. The length of iv must be the same as the
29    /// Block's block size.
30    pub fn new(b: &'a B, iv: &[u8]) -> Self {
31        let blocksize = b.block_size();
32        if iv.len() != blocksize {
33            panic!("cipher.NewCBCEncrypter: IV length must equal block size");
34        }
35        Self {
36            b,
37            block_size: blocksize,
38            iv: iv.to_vec(),
39            tmp: vec![0; blocksize],
40        }
41    }
42
43    pub fn set_iv(&mut self, iv: &[u8]) {
44        if iv.len() != self.iv.len() {
45            panic!("cipher: incorrect length IV");
46        }
47        compat::copy(&mut self.iv, iv);
48    }
49}
50
51impl<'a, B: Block> BlockMode for CBCEncrypter<'a, B> {
52    fn block_size(&self) -> usize {
53        self.block_size
54    }
55
56    fn crypt_blocks(&mut self, dst: &mut [u8], src: &[u8]) {
57        let block_size = self.block_size;
58        if src.len() % block_size != 0 {
59            panic!("crypto/cipher: input not full blocks");
60        }
61        if dst.len() < src.len() {
62            panic!("crypto/cipher: output smaller than input");
63        }
64        if src.is_empty() {
65            return;
66        }
67
68        let tmp = &mut self.tmp;
69
70        // The first block is special because it uses the saved iv.
71        subtle::xor_bytes(tmp, &src[..block_size], &self.iv);
72        self.b.encrypt(&mut dst[..block_size], tmp);
73
74        // The rest is encrypted using the previous block as IV
75        let mut offset = block_size;
76        let mut iv_offset = 0;
77
78        while offset < src.len() {
79            subtle::xor_bytes(
80                tmp,
81                &src[offset..offset + block_size],
82                &dst[iv_offset..iv_offset + block_size],
83            );
84            self.b.encrypt(&mut dst[offset..offset + block_size], tmp);
85            offset += block_size;
86            iv_offset += block_size;
87        }
88        // save the last encrypted block as the IV for the next encryption
89        compat::copy(&mut self.iv, &dst[iv_offset..iv_offset + block_size]);
90    }
91
92    fn crypt_blocks_inplace(&mut self, data: &mut [u8]) {
93        let block_size = self.block_size;
94        if data.len() % block_size != 0 {
95            panic!("crypto/cipher: input not full blocks");
96        }
97        if data.is_empty() {
98            return;
99        }
100
101        let tmp = &mut self.tmp;
102
103        // The first block is special because it uses the saved iv.
104        subtle::xor_bytes(tmp, &data[..block_size], &self.iv);
105        self.b.encrypt(&mut data[..block_size], tmp);
106
107        // The rest is encrypted using the previous block as IV
108        let mut prev = 0;
109        let mut start = block_size;
110        let mut end = start + block_size;
111        while start < data.len() {
112            subtle::xor_bytes(tmp, &data[start..end], &data[prev..start]);
113            self.b.encrypt(&mut data[start..end], tmp);
114            (prev, start, end) = (start, end, end + block_size);
115        }
116        // save the last encrypted block as the IV for the next encryption
117        compat::copy(&mut self.iv, &data[data.len() - block_size..]);
118    }
119}
120
121/// CBCDecrypter implements decryption in cipher block chaining
122/// mode, using the given Block.
123pub struct CBCDecrypter<'a, B: Block> {
124    b: &'a B,
125    block_size: usize,
126    iv: Vec<u8>,
127    tmp: Vec<u8>,
128    tmp_next_iv: Vec<u8>,
129}
130
131impl<'a, B: Block> CBCDecrypter<'a, B> {
132    /// new returns a BlockMode which decrypts in cipher block chaining
133    /// mode, using the given Block. The length of iv must be the same as the
134    /// Block's block size and must match the iv used to encrypt the data.
135    pub fn new(b: &'a B, iv: &[u8]) -> Self {
136        let blocksize = b.block_size();
137        if iv.len() != blocksize {
138            panic!("cipher.NewCBCDecrypter: IV length must equal block size");
139        }
140        Self {
141            b,
142            block_size: blocksize,
143            iv: iv.to_vec(),
144            tmp: vec![0; blocksize],
145            tmp_next_iv: vec![0; blocksize],
146        }
147    }
148
149    pub fn set_iv(&mut self, iv: &[u8]) {
150        if iv.len() != self.iv.len() {
151            panic!("cipher: incorrect length IV");
152        }
153        compat::copy(&mut self.iv, iv);
154    }
155}
156
157impl<'a, B: Block> BlockMode for CBCDecrypter<'a, B> {
158    fn block_size(&self) -> usize {
159        self.block_size
160    }
161
162    fn crypt_blocks(&mut self, dst: &mut [u8], src: &[u8]) {
163        let block_size = self.block_size;
164        if src.len() % block_size != 0 {
165            panic!("crypto/cipher: input not full blocks");
166        }
167        if dst.len() < src.len() {
168            panic!("crypto/cipher: output smaller than input");
169        }
170        if src.is_empty() {
171            return;
172        }
173
174        let tmp = &mut self.tmp;
175
176        // For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv).
177        // The first block is special because it uses the saved iv.
178        self.b.decrypt(tmp, &src[..block_size]);
179        subtle::xor_bytes(&mut dst[..block_size], tmp, &self.iv);
180
181        // The rest is encrypted using the previous block as IV
182        let mut prev = 0;
183        let mut start = block_size;
184        let mut end = start + block_size;
185        while start < src.len() {
186            self.b.decrypt(tmp, &src[start..end]);
187            subtle::xor_bytes(&mut dst[start..end], tmp, &src[prev..start]);
188            (prev, start, end) = (start, end, end + block_size);
189        }
190        // save the last source block as the IV for the next decryption
191        compat::copy(&mut self.iv, &src[src.len() - block_size..]);
192    }
193
194    fn crypt_blocks_inplace(&mut self, data: &mut [u8]) {
195        let block_size = self.block_size;
196        if data.len() % block_size != 0 {
197            panic!("crypto/cipher: input not full blocks");
198        }
199        if data.is_empty() {
200            return;
201        }
202
203        let tmp = &mut self.tmp;
204
205        // For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv).
206        // To avoid making a copy each time, we loop over the blocks BACKWARDS.
207        let mut end = data.len();
208        let mut start = end - block_size;
209        let mut prev = start - block_size;
210
211        // Copy the last block of ciphertext in preparation as the new iv.
212        compat::copy(&mut self.tmp_next_iv, &data[start..end]);
213
214        // Loop over all but the first block.
215        while start > 0 {
216            self.b.decrypt(tmp, &data[start..end]);
217            // we need:
218            //   - data[start..end] as writable
219            //   - data[prev..start] as readable
220            subtle::xor_bytes(
221                unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().add(start), block_size) },
222                unsafe { std::slice::from_raw_parts(data.as_ptr().add(prev), block_size) },
223                tmp,
224            );
225            (end, start, prev) = (start, prev, prev.wrapping_sub(block_size));
226        }
227
228        // The first block is special because it uses the saved iv.
229        self.b.decrypt(tmp, &data[..block_size]);
230        subtle::xor_bytes(&mut data[..block_size], tmp, &self.iv);
231
232        // Set the new iv to the first block we copied earlier.
233        compat::copy(&mut self.iv, &self.tmp_next_iv);
234    }
235}