atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git

Blame: crates/matrix-sdk-crypto/src/ciphers.rs

7ff8adafe9e4atagen2026-10-22 12:30 UTC1
// Copyright 2023 The Matrix.org Foundation C.I.C.
7ff8adafe9e4atagen2026-10-22 12:30 UTC2
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC3
// Licensed under the Apache License, Version 2.0 (the "License");
7ff8adafe9e4atagen2026-10-22 12:30 UTC4
// you may not use this file except in compliance with the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC5
// You may obtain a copy of the License at
7ff8adafe9e4atagen2026-10-22 12:30 UTC6
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC7
//     http://www.apache.org/licenses/LICENSE-2.0
7ff8adafe9e4atagen2026-10-22 12:30 UTC8
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC9
// Unless required by applicable law or agreed to in writing, software
7ff8adafe9e4atagen2026-10-22 12:30 UTC10
// distributed under the License is distributed on an "AS IS" BASIS,
7ff8adafe9e4atagen2026-10-22 12:30 UTC11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7ff8adafe9e4atagen2026-10-22 12:30 UTC12
// See the License for the specific language governing permissions and
7ff8adafe9e4atagen2026-10-22 12:30 UTC13
// limitations under the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC14
7ff8adafe9e4atagen2026-10-22 12:30 UTC15
use aes::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC16
    Aes256,
7ff8adafe9e4atagen2026-10-22 12:30 UTC17
    cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher, generic_array::GenericArray},
7ff8adafe9e4atagen2026-10-22 12:30 UTC18
};
7ff8adafe9e4atagen2026-10-22 12:30 UTC19
use ctr::Ctr128BE;
7ff8adafe9e4atagen2026-10-22 12:30 UTC20
use hkdf::Hkdf;
7ff8adafe9e4atagen2026-10-22 12:30 UTC21
use hmac::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC22
    Hmac, Mac as _,
7ff8adafe9e4atagen2026-10-22 12:30 UTC23
    digest::{FixedOutput, MacError},
7ff8adafe9e4atagen2026-10-22 12:30 UTC24
};
7ff8adafe9e4atagen2026-10-22 12:30 UTC25
use pbkdf2::pbkdf2;
7ff8adafe9e4atagen2026-10-22 12:30 UTC26
use rand::{Rng, rng};
7ff8adafe9e4atagen2026-10-22 12:30 UTC27
use sha2::{Sha256, Sha512};
7ff8adafe9e4atagen2026-10-22 12:30 UTC28
use zeroize::{Zeroize, ZeroizeOnDrop};
7ff8adafe9e4atagen2026-10-22 12:30 UTC29
7ff8adafe9e4atagen2026-10-22 12:30 UTC30
// We could use the `keysize()` method Aes256Ctr as KeySize exposes, but it's
7ff8adafe9e4atagen2026-10-22 12:30 UTC31
// not const (yet?), same for the IV size.
7ff8adafe9e4atagen2026-10-22 12:30 UTC32
pub(crate) const IV_SIZE: usize = 16;
7ff8adafe9e4atagen2026-10-22 12:30 UTC33
pub(crate) const KEY_SIZE: usize = 32;
7ff8adafe9e4atagen2026-10-22 12:30 UTC34
pub(crate) const SALT_SIZE: usize = 16;
7ff8adafe9e4atagen2026-10-22 12:30 UTC35
pub(crate) const MAC_SIZE: usize = 32;
7ff8adafe9e4atagen2026-10-22 12:30 UTC36
7ff8adafe9e4atagen2026-10-22 12:30 UTC37
type Aes256Ctr = Ctr128BE<Aes256>;
7ff8adafe9e4atagen2026-10-22 12:30 UTC38
7ff8adafe9e4atagen2026-10-22 12:30 UTC39
type Aes256Key = GenericArray<u8, <Aes256Ctr as KeySizeUser>::KeySize>;
7ff8adafe9e4atagen2026-10-22 12:30 UTC40
type Aes256Iv = GenericArray<u8, <Aes256Ctr as IvSizeUser>::IvSize>;
7ff8adafe9e4atagen2026-10-22 12:30 UTC41
type HmacSha256Key = [u8; KEY_SIZE];
7ff8adafe9e4atagen2026-10-22 12:30 UTC42
7ff8adafe9e4atagen2026-10-22 12:30 UTC43
/// An authentication tag for the HMAC-SHA-256 message authentication algorithm.
7ff8adafe9e4atagen2026-10-22 12:30 UTC44
#[derive(Debug)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC45
pub(crate) struct HmacSha256Mac([u8; MAC_SIZE]);
7ff8adafe9e4atagen2026-10-22 12:30 UTC46
7ff8adafe9e4atagen2026-10-22 12:30 UTC47
impl HmacSha256Mac {
7ff8adafe9e4atagen2026-10-22 12:30 UTC48
    /// Represent the MAC tag as an array of bytes.
7ff8adafe9e4atagen2026-10-22 12:30 UTC49
    pub(crate) fn as_bytes(&self) -> &[u8; MAC_SIZE] {
7ff8adafe9e4atagen2026-10-22 12:30 UTC50
        &self.0
7ff8adafe9e4atagen2026-10-22 12:30 UTC51
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC52
7ff8adafe9e4atagen2026-10-22 12:30 UTC53
    /// Return the underlying array of bytes of the authentication tag.
7ff8adafe9e4atagen2026-10-22 12:30 UTC54
    pub(crate) fn into_bytes(self) -> [u8; MAC_SIZE] {
7ff8adafe9e4atagen2026-10-22 12:30 UTC55
        self.0
7ff8adafe9e4atagen2026-10-22 12:30 UTC56
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC57
7ff8adafe9e4atagen2026-10-22 12:30 UTC58
    /// Try to create a [`HmacSha256Mac`] from a slice of bytes.
7ff8adafe9e4atagen2026-10-22 12:30 UTC59
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC60
    /// Returns `None` if the length of the byte slice isn't 32 bytes.
7ff8adafe9e4atagen2026-10-22 12:30 UTC61
    pub(crate) fn from_slice(bytes: &[u8]) -> Option<Self> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC62
        if bytes.len() != MAC_SIZE {
7ff8adafe9e4atagen2026-10-22 12:30 UTC63
            None
7ff8adafe9e4atagen2026-10-22 12:30 UTC64
        } else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC65
            let mut mac = [0u8; MAC_SIZE];
7ff8adafe9e4atagen2026-10-22 12:30 UTC66
            mac.copy_from_slice(bytes);
7ff8adafe9e4atagen2026-10-22 12:30 UTC67
7ff8adafe9e4atagen2026-10-22 12:30 UTC68
            Some(HmacSha256Mac(mac))
7ff8adafe9e4atagen2026-10-22 12:30 UTC69
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC70
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC71
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC72
7ff8adafe9e4atagen2026-10-22 12:30 UTC73
/// Keys used for our combination of AES-CTR-256 and HMAC-SHA-256.
7ff8adafe9e4atagen2026-10-22 12:30 UTC74
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC75
/// ⚠️  This struct provides low-level cryptographic primitives.
7ff8adafe9e4atagen2026-10-22 12:30 UTC76
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC77
/// This combination is, as of now, used in the following places:
7ff8adafe9e4atagen2026-10-22 12:30 UTC78
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC79
/// 1. Secret storage[1]
7ff8adafe9e4atagen2026-10-22 12:30 UTC80
/// 2. File-based key exports[2]
7ff8adafe9e4atagen2026-10-22 12:30 UTC81
///
7ff8adafe9e4atagen2026-10-22 12:30 UTC82
/// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
7ff8adafe9e4atagen2026-10-22 12:30 UTC83
/// [2]: https://spec.matrix.org/v1.8/client-server-api/#key-exports
7ff8adafe9e4atagen2026-10-22 12:30 UTC84
#[derive(Zeroize, ZeroizeOnDrop)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC85
pub(crate) struct AesHmacSha2Key {
7ff8adafe9e4atagen2026-10-22 12:30 UTC86
    aes_key: Box<[u8; KEY_SIZE]>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC87
    mac_key: Box<[u8; KEY_SIZE]>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC88
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC89
7ff8adafe9e4atagen2026-10-22 12:30 UTC90
impl AesHmacSha2Key {
7ff8adafe9e4atagen2026-10-22 12:30 UTC91
    /// Create a [`AesHmacSha2Key`] from a passphrase.
7ff8adafe9e4atagen2026-10-22 12:30 UTC92
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC93
    /// The passphrase will be expanded using the algorithm described in the
7ff8adafe9e4atagen2026-10-22 12:30 UTC94
    /// "Key export" part of the [spec].
7ff8adafe9e4atagen2026-10-22 12:30 UTC95
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC96
    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#key-exports
7ff8adafe9e4atagen2026-10-22 12:30 UTC97
    const ZERO_SALT: &'static [u8; 32] = &[0u8; 32];
7ff8adafe9e4atagen2026-10-22 12:30 UTC98
7ff8adafe9e4atagen2026-10-22 12:30 UTC99
    /// Create a per-secret specific [`AesHmacSha2Key`] from the secret storage
7ff8adafe9e4atagen2026-10-22 12:30 UTC100
    /// key.
7ff8adafe9e4atagen2026-10-22 12:30 UTC101
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC102
    /// The secret storage key will be expanded as described in the [spec].
7ff8adafe9e4atagen2026-10-22 12:30 UTC103
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC104
    /// [spec]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
7ff8adafe9e4atagen2026-10-22 12:30 UTC105
    pub(crate) fn from_secret_storage_key(
7ff8adafe9e4atagen2026-10-22 12:30 UTC106
        secret_storage_key: &[u8; KEY_SIZE],
7ff8adafe9e4atagen2026-10-22 12:30 UTC107
        secret_name: &str,
7ff8adafe9e4atagen2026-10-22 12:30 UTC108
    ) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC109
        let mut expanded_keys = [0u8; KEY_SIZE * 2];
7ff8adafe9e4atagen2026-10-22 12:30 UTC110
        let hkdf: Hkdf<Sha256> = Hkdf::new(Some(Self::ZERO_SALT), secret_storage_key);
7ff8adafe9e4atagen2026-10-22 12:30 UTC111
7ff8adafe9e4atagen2026-10-22 12:30 UTC112
        hkdf.expand(secret_name.as_bytes(), &mut expanded_keys)
7ff8adafe9e4atagen2026-10-22 12:30 UTC113
            .expect("We should be able to expand 64 bytes of output key material.");
7ff8adafe9e4atagen2026-10-22 12:30 UTC114
7ff8adafe9e4atagen2026-10-22 12:30 UTC115
        let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
7ff8adafe9e4atagen2026-10-22 12:30 UTC116
7ff8adafe9e4atagen2026-10-22 12:30 UTC117
        expanded_keys.zeroize();
7ff8adafe9e4atagen2026-10-22 12:30 UTC118
7ff8adafe9e4atagen2026-10-22 12:30 UTC119
        Self { aes_key, mac_key }
7ff8adafe9e4atagen2026-10-22 12:30 UTC120
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC121
7ff8adafe9e4atagen2026-10-22 12:30 UTC122
    pub(crate) fn from_passphrase(
7ff8adafe9e4atagen2026-10-22 12:30 UTC123
        passphrase: &str,
7ff8adafe9e4atagen2026-10-22 12:30 UTC124
        pbkdf_rounds: u32,
7ff8adafe9e4atagen2026-10-22 12:30 UTC125
        salt: &[u8; SALT_SIZE],
7ff8adafe9e4atagen2026-10-22 12:30 UTC126
    ) -> Self {
7ff8adafe9e4atagen2026-10-22 12:30 UTC127
        let mut expanded_keys = [0u8; KEY_SIZE * 2];
7ff8adafe9e4atagen2026-10-22 12:30 UTC128
7ff8adafe9e4atagen2026-10-22 12:30 UTC129
        pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt, pbkdf_rounds, &mut expanded_keys)
7ff8adafe9e4atagen2026-10-22 12:30 UTC130
            .expect(
7ff8adafe9e4atagen2026-10-22 12:30 UTC131
                "We should be able to expand a passphrase of any length due to \
7ff8adafe9e4atagen2026-10-22 12:30 UTC132
                 HMAC being able to be initialized with any input size",
7ff8adafe9e4atagen2026-10-22 12:30 UTC133
            );
7ff8adafe9e4atagen2026-10-22 12:30 UTC134
7ff8adafe9e4atagen2026-10-22 12:30 UTC135
        let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
7ff8adafe9e4atagen2026-10-22 12:30 UTC136
7ff8adafe9e4atagen2026-10-22 12:30 UTC137
        expanded_keys.zeroize();
7ff8adafe9e4atagen2026-10-22 12:30 UTC138
7ff8adafe9e4atagen2026-10-22 12:30 UTC139
        Self { aes_key, mac_key }
7ff8adafe9e4atagen2026-10-22 12:30 UTC140
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC141
7ff8adafe9e4atagen2026-10-22 12:30 UTC142
    /// Encrypt the given plaintext and return the ciphertext and the
7ff8adafe9e4atagen2026-10-22 12:30 UTC143
    /// initialization vector.
7ff8adafe9e4atagen2026-10-22 12:30 UTC144
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC145
    /// ⚠️  This method is a low-level cryptographic primitive.
7ff8adafe9e4atagen2026-10-22 12:30 UTC146
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC147
    /// This method does not provide authenticity. You *must* call the
7ff8adafe9e4atagen2026-10-22 12:30 UTC148
    /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
7ff8adafe9e4atagen2026-10-22 12:30 UTC149
    /// create a authentication tag.
7ff8adafe9e4atagen2026-10-22 12:30 UTC150
    pub(crate) fn encrypt(&self, plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC151
        let initialization_vector = Self::generate_iv();
7ff8adafe9e4atagen2026-10-22 12:30 UTC152
        let ciphertext = self.apply_keystream(plaintext, &initialization_vector);
7ff8adafe9e4atagen2026-10-22 12:30 UTC153
7ff8adafe9e4atagen2026-10-22 12:30 UTC154
        (ciphertext, initialization_vector)
7ff8adafe9e4atagen2026-10-22 12:30 UTC155
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC156
7ff8adafe9e4atagen2026-10-22 12:30 UTC157
    /// Apply the keystream to the data stream, producing either the plaintext
7ff8adafe9e4atagen2026-10-22 12:30 UTC158
    /// or the ciphertext depending on whether the data stream is the ciphertext
7ff8adafe9e4atagen2026-10-22 12:30 UTC159
    /// or the plaintext, respectively.
7ff8adafe9e4atagen2026-10-22 12:30 UTC160
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC161
    /// ⚠️  This method is a low-level cryptographic primitive.
7ff8adafe9e4atagen2026-10-22 12:30 UTC162
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC163
    /// If this method is encrypting a plaintext, you *must* ensure that the
7ff8adafe9e4atagen2026-10-22 12:30 UTC164
    /// initialization vector is unique across all calls to this method for
7ff8adafe9e4atagen2026-10-22 12:30 UTC165
    /// a given key.
7ff8adafe9e4atagen2026-10-22 12:30 UTC166
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC167
    /// This method does not provide authenticity. You *must* call the
7ff8adafe9e4atagen2026-10-22 12:30 UTC168
    /// [`AesHmacSha2Key::create_mac_tag()`] method after the encryption step to
7ff8adafe9e4atagen2026-10-22 12:30 UTC169
    /// create a authentication tag or the [`AesHmacSha2Key::verify_mac()`]
7ff8adafe9e4atagen2026-10-22 12:30 UTC170
    /// method before decrypting.
7ff8adafe9e4atagen2026-10-22 12:30 UTC171
    pub(crate) fn apply_keystream(
7ff8adafe9e4atagen2026-10-22 12:30 UTC172
        &self,
7ff8adafe9e4atagen2026-10-22 12:30 UTC173
        mut plaintext: Vec<u8>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC174
        initialization_vector: &[u8; IV_SIZE],
7ff8adafe9e4atagen2026-10-22 12:30 UTC175
    ) -> Vec<u8> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC176
        let mut cipher =
7ff8adafe9e4atagen2026-10-22 12:30 UTC177
            Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(initialization_vector));
7ff8adafe9e4atagen2026-10-22 12:30 UTC178
        cipher.apply_keystream(&mut plaintext);
7ff8adafe9e4atagen2026-10-22 12:30 UTC179
7ff8adafe9e4atagen2026-10-22 12:30 UTC180
        plaintext
7ff8adafe9e4atagen2026-10-22 12:30 UTC181
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC182
7ff8adafe9e4atagen2026-10-22 12:30 UTC183
    /// Create an authentication tag for the given ciphertext.
7ff8adafe9e4atagen2026-10-22 12:30 UTC184
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC185
    /// ⚠️  This method is a low-level cryptographic primitive.
7ff8adafe9e4atagen2026-10-22 12:30 UTC186
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC187
    /// This method *must* be called after a call to
7ff8adafe9e4atagen2026-10-22 12:30 UTC188
    /// [`AesHmacSha2Key::encrypt()`]. The authentication tag must be
7ff8adafe9e4atagen2026-10-22 12:30 UTC189
    /// provided besides the ciphertext for a decryption attempt.
7ff8adafe9e4atagen2026-10-22 12:30 UTC190
    pub(crate) fn create_mac_tag(&self, ciphertext: &[u8]) -> HmacSha256Mac {
7ff8adafe9e4atagen2026-10-22 12:30 UTC191
        let mut mac = [0u8; 32];
7ff8adafe9e4atagen2026-10-22 12:30 UTC192
        let mac_array = GenericArray::from_mut_slice(&mut mac);
7ff8adafe9e4atagen2026-10-22 12:30 UTC193
7ff8adafe9e4atagen2026-10-22 12:30 UTC194
        let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
7ff8adafe9e4atagen2026-10-22 12:30 UTC195
            .expect("We should be able to create a new HMAC object from our 32 byte MAC key");
7ff8adafe9e4atagen2026-10-22 12:30 UTC196
7ff8adafe9e4atagen2026-10-22 12:30 UTC197
        hmac.update(ciphertext);
7ff8adafe9e4atagen2026-10-22 12:30 UTC198
        hmac.finalize_into(mac_array);
7ff8adafe9e4atagen2026-10-22 12:30 UTC199
7ff8adafe9e4atagen2026-10-22 12:30 UTC200
        HmacSha256Mac(mac)
7ff8adafe9e4atagen2026-10-22 12:30 UTC201
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC202
7ff8adafe9e4atagen2026-10-22 12:30 UTC203
    /// Verify an authentication tag for the given, encrypted, message.
7ff8adafe9e4atagen2026-10-22 12:30 UTC204
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC205
    /// You *must* use this method to compare the authentication tags. This
7ff8adafe9e4atagen2026-10-22 12:30 UTC206
    /// method provides a constant-time comparison for the authentication tags.
7ff8adafe9e4atagen2026-10-22 12:30 UTC207
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC208
    /// This method *must* be called before a call to
7ff8adafe9e4atagen2026-10-22 12:30 UTC209
    /// [`AesHmacSha2Key::decrypt()`].
7ff8adafe9e4atagen2026-10-22 12:30 UTC210
    pub(crate) fn verify_mac(&self, message: &[u8], mac: &[u8; MAC_SIZE]) -> Result<(), MacError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC211
        let mac_array = GenericArray::from_slice(mac);
7ff8adafe9e4atagen2026-10-22 12:30 UTC212
7ff8adafe9e4atagen2026-10-22 12:30 UTC213
        let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
7ff8adafe9e4atagen2026-10-22 12:30 UTC214
            .expect("We should be able to create a new HMAC object from our 32 byte MAC key");
7ff8adafe9e4atagen2026-10-22 12:30 UTC215
7ff8adafe9e4atagen2026-10-22 12:30 UTC216
        hmac.update(message);
7ff8adafe9e4atagen2026-10-22 12:30 UTC217
        hmac.verify(mac_array)
7ff8adafe9e4atagen2026-10-22 12:30 UTC218
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC219
7ff8adafe9e4atagen2026-10-22 12:30 UTC220
    /// Decrypt the given ciphertext and return the decrypted plaintext.
7ff8adafe9e4atagen2026-10-22 12:30 UTC221
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC222
    /// The method does not provide authenticity. You *must* call the
7ff8adafe9e4atagen2026-10-22 12:30 UTC223
    /// [`AesHmacSha2Key::verify_mac()`] method before the decryption step to
7ff8adafe9e4atagen2026-10-22 12:30 UTC224
    /// verify the authentication tag.
7ff8adafe9e4atagen2026-10-22 12:30 UTC225
    pub(crate) fn decrypt(
7ff8adafe9e4atagen2026-10-22 12:30 UTC226
        &self,
7ff8adafe9e4atagen2026-10-22 12:30 UTC227
        ciphertext: Vec<u8>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC228
        initialization_vector: &[u8; IV_SIZE],
7ff8adafe9e4atagen2026-10-22 12:30 UTC229
    ) -> Vec<u8> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC230
        self.apply_keystream(ciphertext, initialization_vector)
7ff8adafe9e4atagen2026-10-22 12:30 UTC231
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC232
7ff8adafe9e4atagen2026-10-22 12:30 UTC233
    fn split_keys(
7ff8adafe9e4atagen2026-10-22 12:30 UTC234
        expanded_keys: &[u8; KEY_SIZE * 2],
7ff8adafe9e4atagen2026-10-22 12:30 UTC235
    ) -> (Box<[u8; KEY_SIZE]>, Box<[u8; KEY_SIZE]>) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC236
        let mut aes_key = Box::new([0u8; KEY_SIZE]);
7ff8adafe9e4atagen2026-10-22 12:30 UTC237
        let mut mac_key = Box::new([0u8; KEY_SIZE]);
7ff8adafe9e4atagen2026-10-22 12:30 UTC238
7ff8adafe9e4atagen2026-10-22 12:30 UTC239
        aes_key.copy_from_slice(&expanded_keys[0..32]);
7ff8adafe9e4atagen2026-10-22 12:30 UTC240
        mac_key.copy_from_slice(&expanded_keys[32..64]);
7ff8adafe9e4atagen2026-10-22 12:30 UTC241
7ff8adafe9e4atagen2026-10-22 12:30 UTC242
        (aes_key, mac_key)
7ff8adafe9e4atagen2026-10-22 12:30 UTC243
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC244
7ff8adafe9e4atagen2026-10-22 12:30 UTC245
    /// Generate a new, random initialization vector.
7ff8adafe9e4atagen2026-10-22 12:30 UTC246
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC247
    /// The initialization vector will be clamped and will be used to encrypt
7ff8adafe9e4atagen2026-10-22 12:30 UTC248
    /// the ciphertext.
7ff8adafe9e4atagen2026-10-22 12:30 UTC249
    fn generate_iv() -> [u8; IV_SIZE] {
7ff8adafe9e4atagen2026-10-22 12:30 UTC250
        let mut rng = rng();
7ff8adafe9e4atagen2026-10-22 12:30 UTC251
        let mut iv = [0u8; IV_SIZE];
7ff8adafe9e4atagen2026-10-22 12:30 UTC252
7ff8adafe9e4atagen2026-10-22 12:30 UTC253
        rng.fill_bytes(&mut iv);
7ff8adafe9e4atagen2026-10-22 12:30 UTC254
7ff8adafe9e4atagen2026-10-22 12:30 UTC255
        Self::clamp_iv(iv)
7ff8adafe9e4atagen2026-10-22 12:30 UTC256
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC257
7ff8adafe9e4atagen2026-10-22 12:30 UTC258
    /// The spec tells us to set bit 63 to 0 in some cases for some reason, I'm
7ff8adafe9e4atagen2026-10-22 12:30 UTC259
    /// not sure why, but fine:
7ff8adafe9e4atagen2026-10-22 12:30 UTC260
    ///     Generate 16 random bytes, set bit 63 to 0 (in order to work around
7ff8adafe9e4atagen2026-10-22 12:30 UTC261
    ///     differences in AES-CTR implementations), and use this as the AES
7ff8adafe9e4atagen2026-10-22 12:30 UTC262
    ///     initialization vector. This becomes the iv property, encoded using
7ff8adafe9e4atagen2026-10-22 12:30 UTC263
    ///     base64[1].
7ff8adafe9e4atagen2026-10-22 12:30 UTC264
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC265
    /// [1]: https://spec.matrix.org/v1.8/client-server-api/#msecret_storagev1aes-hmac-sha2
7ff8adafe9e4atagen2026-10-22 12:30 UTC266
    fn clamp_iv(iv: [u8; 16]) -> [u8; IV_SIZE] {
7ff8adafe9e4atagen2026-10-22 12:30 UTC267
        let mut iv = u128::from_be_bytes(iv);
7ff8adafe9e4atagen2026-10-22 12:30 UTC268
        iv &= !(1 << 63);
7ff8adafe9e4atagen2026-10-22 12:30 UTC269
        iv.to_be_bytes()
7ff8adafe9e4atagen2026-10-22 12:30 UTC270
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC271
7ff8adafe9e4atagen2026-10-22 12:30 UTC272
    /// Get the encryption key.
7ff8adafe9e4atagen2026-10-22 12:30 UTC273
    fn aes_key(&self) -> &Aes256Key {
7ff8adafe9e4atagen2026-10-22 12:30 UTC274
        Aes256Key::from_slice(self.aes_key.as_slice())
7ff8adafe9e4atagen2026-10-22 12:30 UTC275
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC276
7ff8adafe9e4atagen2026-10-22 12:30 UTC277
    /// Get the authentication key.
7ff8adafe9e4atagen2026-10-22 12:30 UTC278
    fn mac_key(&self) -> &HmacSha256Key {
7ff8adafe9e4atagen2026-10-22 12:30 UTC279
        &self.mac_key
7ff8adafe9e4atagen2026-10-22 12:30 UTC280
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC281
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC282
7ff8adafe9e4atagen2026-10-22 12:30 UTC283
#[cfg(test)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC284
mod test {
7ff8adafe9e4atagen2026-10-22 12:30 UTC285
    use super::*;
7ff8adafe9e4atagen2026-10-22 12:30 UTC286
7ff8adafe9e4atagen2026-10-22 12:30 UTC287
    #[test]
7ff8adafe9e4atagen2026-10-22 12:30 UTC288
    fn encryption_roundtrip() {
7ff8adafe9e4atagen2026-10-22 12:30 UTC289
        let plaintext = "It's a secret to everybody";
7ff8adafe9e4atagen2026-10-22 12:30 UTC290
7ff8adafe9e4atagen2026-10-22 12:30 UTC291
        let salt = [0u8; SALT_SIZE];
7ff8adafe9e4atagen2026-10-22 12:30 UTC292
        let key = AesHmacSha2Key::from_passphrase("My passphrase", 10, &salt);
7ff8adafe9e4atagen2026-10-22 12:30 UTC293
7ff8adafe9e4atagen2026-10-22 12:30 UTC294
        let (ciphertext, iv) = key.encrypt(plaintext.as_bytes().to_vec());
7ff8adafe9e4atagen2026-10-22 12:30 UTC295
        let mac = key.create_mac_tag(&ciphertext);
7ff8adafe9e4atagen2026-10-22 12:30 UTC296
7ff8adafe9e4atagen2026-10-22 12:30 UTC297
        key.verify_mac(&ciphertext, mac.as_bytes())
7ff8adafe9e4atagen2026-10-22 12:30 UTC298
            .expect("The MAC tag should be successfully verified");
7ff8adafe9e4atagen2026-10-22 12:30 UTC299
        let decrypted = key.decrypt(ciphertext, &iv);
7ff8adafe9e4atagen2026-10-22 12:30 UTC300
7ff8adafe9e4atagen2026-10-22 12:30 UTC301
        assert_eq!(
7ff8adafe9e4atagen2026-10-22 12:30 UTC302
            plaintext.as_bytes(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC303
            decrypted,
7ff8adafe9e4atagen2026-10-22 12:30 UTC304
            "An encryption roundtrip should produce the same plaintext"
7ff8adafe9e4atagen2026-10-22 12:30 UTC305
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC306
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC307
7ff8adafe9e4atagen2026-10-22 12:30 UTC308
    #[test]
7ff8adafe9e4atagen2026-10-22 12:30 UTC309
    fn mac_decoding() {
7ff8adafe9e4atagen2026-10-22 12:30 UTC310
        let invalid_mac = [0u8; 10];
7ff8adafe9e4atagen2026-10-22 12:30 UTC311
7ff8adafe9e4atagen2026-10-22 12:30 UTC312
        assert!(
7ff8adafe9e4atagen2026-10-22 12:30 UTC313
            HmacSha256Mac::from_slice(&invalid_mac).is_none(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC314
            "We should return an error if the MAC is too short"
7ff8adafe9e4atagen2026-10-22 12:30 UTC315
        );
7ff8adafe9e4atagen2026-10-22 12:30 UTC316
7ff8adafe9e4atagen2026-10-22 12:30 UTC317
        let mac = [0u8; 32];
7ff8adafe9e4atagen2026-10-22 12:30 UTC318
7ff8adafe9e4atagen2026-10-22 12:30 UTC319
        HmacSha256Mac::from_slice(&mac)
7ff8adafe9e4atagen2026-10-22 12:30 UTC320
            .expect("We should be able to create a MAC from a 32 byte long slice");
7ff8adafe9e4atagen2026-10-22 12:30 UTC321
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC322
}