Docs and cleanup.

This commit is contained in:
Adam Ierymenko 2022-04-08 11:01:17 -04:00
parent 1fa5a0dc5e
commit e22afcd23b
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
4 changed files with 2256 additions and 73 deletions

View file

@ -1,22 +1,13 @@
[package]
name = "aes-gmac-siv"
version = "0.1.0"
edition = "2018"
version = "0.5.0"
edition = "2021"
license = "BSD"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = 'abort'
[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies]
openssl = "^0"
#[target."cfg(all(not(any(target_os = \"macos\", target_os = \"ios\")), any(target_arch = \"s390x\", target_arch = \"powerpc64le\", target_arch = \"powerpc64\")))".dependencies]
#openssl = "^0"
#[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\", target_arch = \"s390x\", target_arch = \"powerpc64le\", target_arch = \"powerpc64\")))".dependencies]
#[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies]
#gcrypt = "^0"
[dev-dependencies]

View file

@ -1,39 +1,36 @@
AES-GMAC-SIV
======
Rust implementation for ZeroTier 2.0.
Rust implementation of AES-GMAC-SIV, a FIPS-compliant SIV mode for AES-256, with underlying cryptographic primitives provided by either macOS CryptoCore, OpenSSL, or (eventually, not implemented yet) WinCrypt. The appropriate API is automatically selected at compile time.
*WARNING: while the AES-GMAC-SIV construction has been subjected to peer review, this code has not (yet!). Use at your own risk.*
This is designed for use with the ZeroTier protocol but could be used for other message based protocols where a secure authenticated cipher construction is needed and where a FIPS compliant mode with less catastrophic failure modes than AES-GCM is desired.
## Introduction
AES-GMAC-SIV is a "synthetic IV" (SIV) cipher construction implemented using only FIPS140 and NIST accepted cryptographic building blocks: AES-ECB (used to mix input IV and GMAC result), AES-CTR, and GMAC (the MAC part of GCM, which can also be used separately).
AES-GMAC-SIV is a "synthetic IV" (SIV) cipher construction implemented using only FIPS and NIST accepted cryptographic building blocks: AES-CTR, AES-ECB (one block), and GMAC (the MAC component of GCM).
AES-GMAC-SIV is almost identical to [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV), but that mode uses a non-standard MAC called POLYVAL in place of GMAC. POLYVAL is basically little-endian GMAC but the fact that it is not standard GMAC means it's not found in most cryptographic libraries and is not approved by FIPS140 and many other sets of compliance guidelines.
AES-GMAC-SIV is similar to [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV), but that mode uses a non-standard MAC called POLYVAL in place of GMAC. POLYVAL is just GMAC in little-endian, but the fact that it is not standard GMAC means it's not found in most cryptographic libraries and is not approved by FIPS and most other cryptographic standards. The only real difference here is the use of standard GMAC and a 64-bit salt to match ZeroTier's use of a 64-bit message ID counter as an initialization vector.
The use of standard AES and GMAC also means that the AES-GCM oriented hardware accelerators on most larger desktop, server, and mobile CPUs can be used. This results in excellent performance, often beating ChaCha20/Poly1305 where hardware acceleration is available.
## Why SIV? Why not just GCM?
Stream ciphers like AES-CTR, ChaCha20, and others require a number called an initialization vector (IV) for each use. The IV is sometimes called a nonce, or *number used once*, because using the same value for different messages with the same key is a major no-no.
Stream ciphers like AES-CTR, ChaCha20, and others require a number called an initialization vector (IV) for each use. These and most other stream ciphers work by XORing a key stream with plaintext, so if an IV is used more than once security is compromised. Since XOR is commutative, if two different messages are encrypted with the same key stream a simple XOR can reveal that key stream and decrypt both messages. This is a common pitfall with any XOR based symmetric cipher construction.
Repeating an IV/nonce with the same key allows both messages to be decrypted. This is because XOR, which is used to apply the stream cipher's pseudorandom bits as a one time pad, is commutative. Repeating a nonce can in some cases also allow an attacker to attack the MAC (e.g. GMAC or Poly1305) and forge messages that will appear valid. Message forgery is often more serious than loss of plaintext secrecy for a few messages as it permits active attacks.
Repeating the IV is dangerous with many MAC functions in authenticated constructions as well. For AES-GCM the use of a duplicate IV with GMAC can allow an attacker to forge messages, which for many use cases is a worse security failure than letting an attacker decrypt a few messages.
SIV modes provide strong protection against IV reuse by generating a *synthetic IV* from the plaintext. This means that two different plaintexts will result in different IVs even if the input IV is duplicated.
With SIV a duplicate IV has no effect at all except in the case where the same IV is used to encrypt the same message twice. In this case the encrypted messages would also be identical, revealing that a duplicate was sent, but because both the IV and message are the same this would not compromise security like IV reuse does in standard modes. It's technically possible to use SIV modes with no IV at all without compromising plaintext secrecy, but this isn't recommended as it would still always leak message duplication.
We recommend treating AES-GMAC-SIV (and other SIV modes) as if they were normal stream ciphers and endeavoring to make the IV unique as those would require.
SIV modes provide strong protection against IV reuse and improve security bounds for protocols using small IVs (like ZeroTier's 64-bit message ID) by generating a larger *synthetic IV* from a random or sequential external IV and the message plaintext. Even if the externally provided IV is duplicated, the larger SIV IV won't repeat unless the plaintext is also identical. In this case all you've done is leaked the fact that you have sent a duplicate message. The security of that message and its authentication function is not compromised.
SIV modes might seem like paranoia, but accidental IV reuse is easier than you might think. Here's a few scenarios where it might happen:
* Live cloning of virtual machines or application state, resulting in two clones with identical random number generator states.
* Embedded devices that initialize PRNGs from deterministic sources.
* Forgetting to use atomics or a mutex to synchronize an IV counter variable in multithreaded code.
* Edge case memory model differences between processors affecting synchronization of an IV counter variable.
* Concurrent use of a non-thread-safe random number generator.
* Multiple dependencies in a project stomping on the same random number generator state.
* Backing up and restoring counter or random seed files.
* Live cloning of virtual machines or application state, resulting in two clones with identical counters or random number generator states.
* Forgetting to use atomics or a mutex to synchronize an IV counter or a non-thread-safe random number generator.
* Hardware differences in concurrent memory access behavior causing synchronization issues when code is ported to new architectures.
* Embedded devices that lack a good source of randomness or have poorly implemented random initialization.
* Memory errors that corrupt an IV counter variable, especially if they can be triggered remotely.
* "Rowhammer" and similar hardware attacks targeting the IV counter.
* Time changes or attacks against NTP if a clock is used as input in genreating an IV.
* Time changes or attacks against NTP if a clock is used as an input in initializing a counter or random source.
* Rapid restarts of a service where clock is used as an input in IV initialization.
... and so on. "Sudden death" on IV re-use is a foot-gun that's worth removing.
@ -41,47 +38,42 @@ SIV modes might seem like paranoia, but accidental IV reuse is easier than you m
![AES-GMAC-SIV block diagram](AES-GMAC-SIV.png)
Initialization parameters:
Two initialization keys, which can be derived from one using a parameterized KDF:
1. K0, an AES key used to initialize AES-GMAC.
2. K1, a second (and different) AES key used to initialize AES-ECB and AES-CTR.
1. K0, a 256-bit AES key used to initialize AES-GMAC.
2. K1, a second (and different) 256-bit AES key used to initialize AES-ECB and AES-CTR.
Per-message parameters:
1. A per-message unique 64-bit IV (can be a counter or random).
1. A per-message unique 64-bit IV (can be a counter or random, though a non-overlapping counter is preferred).
2. Optional additional associated data (AAD) to authenticate but not encrypt.
3. A plaintext message to encrypt.
Encryption steps:
1. Pad 64-bit IV to 96 bits and use it to initialize GMAC.
2. Feed AAD (if any) into GMAC.
1. Pad 64-bit IV to 96 bits with zeroes and use it to initialize GMAC.
2. Feed AAD (if any) into GMAC prior to plaintext.
3. Pad AAD length to a multiple of 16 by feeding zeroes into GMAC to ensure unique encoding.
4. Feed plaintext into GMAC to compute final MAC.
5. XOR lower 64 bits and higher 64 bits of GMAC tag.
6. Concatenate original input IV and 64-bit shortened tag to form a 128-bit block.
7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV.
8. Clear bit 31 (from the right) in the tag and use this to initialize AES-CTR. Bit 31 is cleared so AES-CTR implementations that use a 32-bit counter will not overflow for messages less than 2^31 bytes in length.
9. Encrypt plaintext with AES-CTR.
The message tag is the 128-bit encrypted block from step 7 before bit 31 is cleared. Only this 128-bit tag needs to be sent with the message. The IV supplied for encryption should not be sent, as it's obtained by decrypting the tag.
5. XOR lower 64 bits and higher 64 bits of 128-bit GMAC tag to yield a 64-bit tag.
6. Concatenate original 64-bit input IV and 64-bit shortened tag to form a 128-bit block.
7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV. (AES-ECB is perfectly safe if only one block is encrypted.)
8. Clear bit 31 (from the right) in the tag and use this to initialize AES-CTR with the first 96 bits being the AES-CTR IV and the remaining 31 bits being the AES-CTR "index" or counter. This provides what amounts to a 127-bit AES-CTR IV. The most significant bit of the counter is cleared so that poor quality AES-CTR implementations that only use a 32-bit wrapping counter will not wrap at message sizes up to 2^31 bytes. Wrapping technically wouldn't hurt anything unless the implementation generates a fault on wrap, but avoid this in case some cryptographic accelerator somewhere does so.
9. Encrypt plaintext with AES-CTR and send this along with the encrypted IV+tag from step 7 (without CTR counter bit 31 cleared). The per-message unique 64-bit IV supplied by the caller at encryption **should not** be sent as it is recovered during decryption by decrypting the IV+tag blob. Sending it wastes space and reveals slightly more state information to an attacker, since without the input IV an attacker doesn't know if it has in fact been duplicated.
Decryption steps:
1. Initialize AES-CTR with the tag after clearning bit 31 as in step 8 above.
*Decryption reverses encryption steps 8, 9, 7, 1, 2, 3, 4, and 5, then authenticates.*
1. Initialize AES-CTR IV with the tag after clearning bit 31 as in step 8 above.
2. Decrypt ciphertext with AES-CTR.
3. AES-ECB *decrypt* 128-bit tag to yield original IV and 64-bit shortened GMAC tag.
4. Initialize GMAC as in encryption step 1 using first 64 bits of the decrypted message tag from step 3.
5. Feed AAD into GMAC (if any).
6. Zero-pad AAD to a multiple of 16 as in encryption.
4. Initialize GMAC using first 64 bits of the *decrypted* message tag and zero pad to 96 bits (as in encryption step 1).
5. Feed AAD into GMAC (if any) (as in encryption step 2).
6. Zero-pad AAD to a multiple of 16 (as in encryption step 3).
7. Feed *decrypted plaintext* into GMAC.
8. Generate GMAC tag, XOR least and most significant 64 bits, and check MAC. Discard packet if these do not match.
Notes:
* The extra step (7 in encrypt, 3 in decrypt) of AES-ECB encrypting (mixing) the bits of the input IV and GMAC tag is there because two identical plaintexts would yield identical GMAC tags. Without this single block encryption step this would leak plaintext duplication to anyone monitoring traffic. This step has no security impact on AES-CTR.
* Clearing of bit 31 in the AES-CTR IV as in step 8 of encryption is technically not required but is there for compatibility with some AES-CTR implementations. If message size exceeds (16 * 2^31) bytes this becomes moot, but that's huge for a single message. If an AES-CTR implementation supports 64-bit or 128-bit counter semantics then this construction can be used for messages of effectively unlimited size.
## Performance
Performance is very close to AES-GCM on a given platform. It's very slightly slower because encryption requires two passes, but for short messages the second pass will operate on data already in the CPU's L0 cache which minimizes the actual overhead.
@ -93,22 +85,18 @@ You can run tests with `cargo test --release -- --nocapture` and see encrypt and
In general this construction performs better than ChaChaPoly or other ARX ciphers on processors that have AES hardware acceleration and considerably worse on processors that lack it. Performance on systems without hardware acceleration is generally still good enough for most applications.
## Cryptanalysis
## Security
[Trail of Bits](https://www.trailofbits.com) analyzed AES-GMAC-SIV as part of their [design analysis of ZeroTier 2.0](https://github.com/trailofbits/publications/blob/master/reviews/ZeroTierProtocol.pdf) and concluded that its security is equivalent to the original AES-SIV and AES-GCM-SIV constructions.
The algorithms on which this is built, namely AES, AES-CTR, and GMAC, are well known and standard.
## FIPS-140 Compliance
## FIPS Compliance
AES-ECB, AES-CTR, and GMAC are all algorithms allowed by FIPS-140. For FIPS purposes AES-GMAC-SIV would be described as AES-CTR with GMAC authentication. Since this is built out of compliant components, a standard FIPS-certified cryptographic library could be used.
## Dependencies
This is implemented using the [libgcrypt](https://github.com/gpg/libgcrypt) library (via Rust bindings) on Linux, BSD, and Windows, and built-in CommonCrypto libraries on MacOS and iOS. CommonCrypto was used on Mac because libgcrypt has issues on the ARM64 platform with Apple's clang. An OpenSSL based implementation is also included but it is only used on a few platforms where libgcrypt does not yet support native hardware acceleration, such as IBM S390x.
AES-CTR, AES-ECB, and GMAC are all algorithms allowed by FIPS-140. For FIPS purposes AES-GMAC-SIV would be described as AES-CTR with GMAC authentication. Since this is built out of entirely standard building blocks, a FIPS certified cryptographic library can be used to implement it.
## License
This Rust implementation of AES-GMAC-SIV is released under the BSD 2-clause license.
(c) 2021 ZeroTier, Inc.
(c) 2021-2022 ZeroTier, Inc.

View file

@ -9,30 +9,28 @@
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod impl_macos;
//#[cfg(not(any(target_os = "macos", target_os = "ios", target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
//mod impl_gcrypt;
//#[cfg(all(not(any(target_os = "macos", target_os = "ios")), any(target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
mod impl_openssl;
//#[cfg(not(any(target_os = "macos", target_os = "ios")))]
//mod impl_gcrypt;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub use impl_macos::{AesCtr, AesGmacSiv};
//#[cfg(not(any(target_os = "macos", target_os = "ios", target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
//pub use impl_gcrypt::{AesCtr, AesGmacSiv};
//#[cfg(all(not(any(target_os = "macos", target_os = "ios")), any(target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub use impl_openssl::{AesCtr, AesGmacSiv};
//#[cfg(not(any(target_os = "macos", target_os = "ios")))]
//pub use impl_gcrypt::{AesCtr, AesGmacSiv};
pub(crate) const ZEROES: [u8; 16] = [0_u8; 16];
#[cfg(test)]
mod tests {
use crate::AesGmacSiv;
use std::time::SystemTime;
use sha2::Digest;
use std::time::SystemTime;
fn to_hex(b: &[u8]) -> String {
let mut s = String::new();
@ -44,9 +42,9 @@ mod tests {
#[test]
fn encrypt_decrypt() {
let aes_key_0: [u8; 32] = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32];
let aes_key_1: [u8; 32] = [2,3,4,5,6,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32];
let iv: [u8; 8] = [0,1,2,3,4,5,6,7];
let aes_key_0: [u8; 32] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32];
let aes_key_1: [u8; 32] = [2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32];
let iv: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
let mut buf = [0_u8; 12345];
for i in 1..12345 {

2206
zerotier-system-service/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff