diff --git a/.github/workflows/cfb-mode.yml b/.github/workflows/cfb-mode.yml deleted file mode 100644 index a6d1f239..00000000 --- a/.github/workflows/cfb-mode.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: cfb-mode - -on: - pull_request: - paths: - - "cfb-mode/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: cfb-mode - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test - - run: cargo test --release diff --git a/.github/workflows/cfb8.yml b/.github/workflows/cfb8.yml deleted file mode 100644 index be826647..00000000 --- a/.github/workflows/cfb8.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: cfb8 - -on: - pull_request: - paths: - - "cfb8/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: cfb8 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test - - run: cargo test --release diff --git a/.github/workflows/chacha20.yml b/.github/workflows/chacha20.yml index 654e8e74..c7192115 100644 --- a/.github/workflows/chacha20.yml +++ b/.github/workflows/chacha20.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: rust: - - 1.51.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -38,11 +38,8 @@ jobs: target: ${{ matrix.target }} override: true profile: minimal - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features cipher - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features hchacha - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features legacy - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features rng - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features cipher,force-soft,legacy,rng,zeroize + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features zeroize # Tests for runtime AVX2 detection autodetect: @@ -52,7 +49,7 @@ jobs: include: # 32-bit Linux - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV deps: sudo apt update && sudo apt install gcc-multilib - target: i686-unknown-linux-gnu rust: stable @@ -60,7 +57,7 @@ jobs: # 64-bit Linux - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable steps: @@ -73,23 +70,20 @@ jobs: profile: minimal - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} --release - - run: cargo test --target ${{ matrix.target }} --release --features std - - run: cargo test --target ${{ matrix.target }} --release --features rng - - run: cargo test --target ${{ matrix.target }} --release --features zeroize - - run: cargo test --target ${{ matrix.target }} --release --features std,rng,zeroize + - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features std,zeroize # Tests for the AVX2 backend avx2: runs-on: ubuntu-latest env: - RUSTFLAGS: -Ctarget-cpu=haswell -Dwarnings # Enables `avx2` target feature + RUSTFLAGS: -Ctarget-feature=+avx2 -Dwarnings strategy: matrix: include: # 32-bit Linux - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV deps: sudo apt update && sudo apt install gcc-multilib - target: i686-unknown-linux-gnu rust: stable @@ -97,7 +91,7 @@ jobs: # 64-bit Linux - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable steps: @@ -110,22 +104,20 @@ jobs: override: true - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} --release - - run: cargo test --target ${{ matrix.target }} --release --features force-soft - - run: cargo test --target ${{ matrix.target }} --release --features std - - run: cargo test --target ${{ matrix.target }} --release --features rng - - run: cargo test --target ${{ matrix.target }} --release --features zeroize - - run: cargo test --target ${{ matrix.target }} --release --all-features + - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features std,zeroize # Tests for the portable software backend (i.e. `force-soft`) soft: runs-on: ubuntu-latest + env: + RUSTFLAGS: --cfg chacha20_force_soft -Dwarnings strategy: matrix: include: # 32-bit Linux - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV deps: sudo apt update && sudo apt install gcc-multilib - target: i686-unknown-linux-gnu rust: stable @@ -133,7 +125,7 @@ jobs: # 64-bit Linux - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable steps: @@ -146,11 +138,8 @@ jobs: override: true - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} --release --features force-soft - - run: cargo test --target ${{ matrix.target }} --release --features force-soft,std - - run: cargo test --target ${{ matrix.target }} --release --features force-soft,rng - - run: cargo test --target ${{ matrix.target }} --release --features force-soft,rng,zeroize - - run: cargo test --target ${{ matrix.target }} --release --all-features + - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features std,zeroize # Cross-compiled tests cross: @@ -159,7 +148,7 @@ jobs: include: # ARM64 - target: aarch64-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: aarch64-unknown-linux-gnu rust: stable @@ -170,11 +159,15 @@ jobs: # PPC32 - target: powerpc-unknown-linux-gnu - rust: 1.51.0 # MSRV + rust: 1.56.0 # MSRV - target: powerpc-unknown-linux-gnu rust: stable runs-on: ubuntu-latest + defaults: + run: + # Cross mounts only current package, i.e. by default it ignores workspace's Cargo.toml + working-directory: . steps: - uses: actions/checkout@v1 - run: ${{ matrix.deps }} @@ -184,9 +177,12 @@ jobs: target: ${{ matrix.target }} profile: minimal override: true - - run: cargo install cross - - run: cross test --target ${{ matrix.target }} --release ${{ matrix.features }} - - run: cross test --target ${{ matrix.target }} --release --features force-soft - - run: cross test --target ${{ matrix.target }} --release --features rng - - run: cross test --target ${{ matrix.target }} --release --features std - - run: cross test --target ${{ matrix.target }} --release --all-features + - name: Install precompiled cross + run: | + export URL=$(curl -s https://api.github.com/repos/cross-rs/cross/releases/latest | \ + jq -r '.assets[] | select(.name | contains("x86_64-unknown-linux-gnu.tar.gz")) | .browser_download_url') + wget -O /tmp/binaries.tar.gz $URL + tar -C /tmp -xzf /tmp/binaries.tar.gz + mv /tmp/cross ~/.cargo/bin + shell: bash + - run: cross test --package chacha20 --target ${{ matrix.target }} ${{ matrix.features }} diff --git a/.github/workflows/ctr.yml b/.github/workflows/ctr.yml deleted file mode 100644 index 1e7aa2f8..00000000 --- a/.github/workflows/ctr.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: ctr - -on: - pull_request: - paths: - - "ctr/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: ctr - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test - - run: cargo test --release diff --git a/.github/workflows/hc-256.yml b/.github/workflows/hc-256.yml index fc2bc2ce..79ec6e1c 100644 --- a/.github/workflows/hc-256.yml +++ b/.github/workflows/hc-256.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -35,15 +35,15 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - - run: cargo build --release --target ${{ matrix.target }} - - run: cargo build --release --target ${{ matrix.target }} --features zeroize + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features zeroize test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - uses: actions/checkout@v1 @@ -53,4 +53,4 @@ jobs: toolchain: ${{ matrix.rust }} override: true - run: cargo test - - run: cargo test --release + - run: cargo test --all-features diff --git a/.github/workflows/ofb.yml b/.github/workflows/ofb.yml deleted file mode 100644 index 4fd0e647..00000000 --- a/.github/workflows/ofb.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: ofb - -on: - pull_request: - paths: - - "ofb/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: ofb - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --no-default-features --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.41.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test - - run: cargo test --release diff --git a/.github/workflows/rabbit.yml b/.github/workflows/rabbit.yml index af4849c5..4e814df7 100644 --- a/.github/workflows/rabbit.yml +++ b/.github/workflows/rabbit.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -35,15 +35,15 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - - run: cargo build --release --target ${{ matrix.target }} - - run: cargo build --release --target ${{ matrix.target }} --features zeroize + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features zeroize test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - uses: actions/checkout@v1 @@ -53,4 +53,4 @@ jobs: toolchain: ${{ matrix.rust }} override: true - run: cargo test - - run: cargo test --release + - run: cargo test --all-features diff --git a/.github/workflows/salsa20.yml b/.github/workflows/salsa20.yml index cc287a75..84964e18 100644 --- a/.github/workflows/salsa20.yml +++ b/.github/workflows/salsa20.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -35,15 +35,15 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - - run: cargo build --release --target ${{ matrix.target }} - - run: cargo build --release --target ${{ matrix.target }} --features zeroize + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features zeroize test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - uses: actions/checkout@v1 @@ -53,4 +53,4 @@ jobs: toolchain: ${{ matrix.rust }} override: true - run: cargo test - - run: cargo test --release + - run: cargo test --all-features diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 817ccbd0..4ad636f5 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 # MSRV (highest in repo) + toolchain: 1.56.0 # MSRV components: clippy override: true profile: minimal diff --git a/.gitignore b/.gitignore index 18443261..b9d396dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ target/ -benches/Cargo.lock +**/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 95f486bf..4ef3527d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,40 +3,19 @@ # version = 3 -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "opaque-debug", -] - [[package]] name = "blobby" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc52553543ecb104069b0ff9e0fcc5c739ad16202935528a112d974e8f1a4ee8" +checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" [[package]] -name = "cfb-mode" -version = "0.7.1" -dependencies = [ - "aes", - "cipher", - "hex-literal", -] - -[[package]] -name = "cfb8" -version = "0.7.1" +name = "block-padding" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5808df4b2412175c4db3afb115c83d8d0cd26ca4f30a042026cddef8580e526a" dependencies = [ - "aes", - "cipher", - "hex-literal", + "generic-array", ] [[package]] @@ -47,24 +26,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.1" +version = "0.9.0" dependencies = [ "cfg-if", "cipher", "cpufeatures", "hex-literal", - "rand_core", - "zeroize", ] [[package]] name = "cipher" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "a4f3e8c9be82c31c331bc9db0fd70a1068f8a288d980b2414dcaa25ab17ac1e0" dependencies = [ "blobby", - "generic-array", + "crypto-common", + "inout", + "zeroize", ] [[package]] @@ -77,21 +56,19 @@ dependencies = [ ] [[package]] -name = "ctr" -version = "0.8.0" +name = "crypto-common" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" dependencies = [ - "aes", - "cipher", - "hex-literal", - "kuznyechik", - "magma", + "generic-array", ] [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -99,175 +76,64 @@ dependencies = [ [[package]] name = "hc-256" -version = "0.4.1" +version = "0.5.0" dependencies = [ "cipher", - "zeroize", + "hex-literal", ] [[package]] name = "hex-literal" -version = "0.2.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" -dependencies = [ - "hex-literal-impl", - "proc-macro-hack", -] - -[[package]] -name = "hex-literal-impl" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f769599eb31de176303197b7ba4973299c38c7a7604a6bc88c3eef05b9b46" -dependencies = [ - "proc-macro-hack", -] +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] -name = "kuznyechik" -version = "0.7.2" +name = "inout" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4e0a85306cf7cdcd497111b9ecd8df4da5290bacd3cc2f426ce3fb2c0a327e" +checksum = "c1d8734d7f28aaff861d726dc3bc8003e2987d2fc26add21f5dab0c35d5c348a" dependencies = [ - "cipher", + "block-padding", + "generic-array", ] [[package]] name = "libc" -version = "0.2.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" - -[[package]] -name = "magma" -version = "0.7.0" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53792c7fca348c3d880c5ab2b0a2378a28edca57d080feabc4b60b4633dff91b" -dependencies = [ - "cipher", - "opaque-debug", -] - -[[package]] -name = "ofb" -version = "0.5.1" -dependencies = [ - "aes", - "cipher", - "hex-literal", -] - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "rabbit" -version = "0.3.1" +version = "0.4.0" dependencies = [ "cipher", - "zeroize", + "hex-literal", ] -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" - [[package]] name = "salsa20" -version = "0.9.0" +version = "0.10.0" dependencies = [ "cipher", - "zeroize", -] - -[[package]] -name = "syn" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "hex-literal", ] [[package]] name = "typenum" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" - -[[package]] -name = "unicode-xid" -version = "0.2.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "zeroize" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.1.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] +checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" diff --git a/Cargo.toml b/Cargo.toml index a3707e5a..5b02c8ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [workspace] members = [ - "cfb8", - "cfb-mode", "chacha20", - "ctr", "hc-256", - "ofb", "rabbit", "salsa20", ] + +[profile.dev] +opt-level = 2 diff --git a/README.md b/README.md index 26d1abd4..d0610b28 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# RustCrypto: stream ciphers [![Project Chat][chat-image]][chat-link] [![dependency status][deps-image]][deps-link] [![HAZMAT][hazmat-image]][hazmat-link] +# RustCrypto: stream ciphers -Collection of [stream cipher][1] algorithms written in pure Rust. +[![Project Chat][chat-image]][chat-link] +[![dependency status][deps-image]][deps-link] +![Apache2/MIT licensed][license-image] +[![HAZMAT][hazmat-image]][hazmat-link] + +Collection of [stream ciphers] written in pure Rust. ## ⚠️ Security Warning: [Hazmat!][hazmat-link] @@ -14,59 +19,59 @@ received any formal cryptographic and security reviews/audits. **USE AT YOUR OWN RISK!** ## Crates -| Name | Crates.io | Documentation | MSRV | -|--------------|-----------|---------------|------| -| [`cfb-mode`] | [![crates.io](https://img.shields.io/crates/v/cfb-mode.svg)](https://crates.io/crates/cfb-mode) | [![Documentation](https://docs.rs/cfb-mode/badge.svg)](https://docs.rs/cfb-mode) | 1.41 | -| [`cfb8`] | [![crates.io](https://img.shields.io/crates/v/cfb8.svg)](https://crates.io/crates/cfb8) | [![Documentation](https://docs.rs/cfb8/badge.svg)](https://docs.rs/cfb8) | 1.41 | -| [`chacha20`] | [![crates.io](https://img.shields.io/crates/v/chacha20.svg)](https://crates.io/crates/chacha20) | [![Documentation](https://docs.rs/chacha20/badge.svg)](https://docs.rs/chacha20) | 1.51 | -| [`ctr`] | [![crates.io](https://img.shields.io/crates/v/ctr.svg)](https://crates.io/crates/ctr) | [![Documentation](https://docs.rs/ctr/badge.svg)](https://docs.rs/ctr) | 1.41 | -| [`hc-256`] | [![crates.io](https://img.shields.io/crates/v/hc-256.svg)](https://crates.io/crates/hc-256) | [![Documentation](https://docs.rs/hc-256/badge.svg)](https://docs.rs/hc-256) | 1.41 | -| [`ofb`] | [![crates.io](https://img.shields.io/crates/v/ofb.svg)](https://crates.io/crates/ofb) | [![Documentation](https://docs.rs/ofb/badge.svg)](https://docs.rs/ofb) | 1.41 | -| [`rabbit`] | [![crates.io](https://img.shields.io/crates/v/rabbit.svg)](https://crates.io/crates/rabbit) | [![Documentation](https://docs.rs/rabbit/badge.svg)](https://docs.rs/rabbit) | 1.41 | -| [`salsa20`] | [![crates.io](https://img.shields.io/crates/v/salsa20.svg)](https://crates.io/crates/salsa20) | [![Documentation](https://docs.rs/salsa20/badge.svg)](https://docs.rs/salsa20) | 1.41 | +| Name | Crate name | Crates.io | Docs | MSRV | +|----------|------------|-----------|------|------| +| [ChaCha] | [`chacha20`] | [![crates.io](https://img.shields.io/crates/v/chacha20.svg)](https://crates.io/crates/chacha20) | [![Documentation](https://docs.rs/chacha20/badge.svg)](https://docs.rs/chacha20) | ![MSRV 1.56][msrv-1.56] | +| [HC-256] | [`hc-256`] | [![crates.io](https://img.shields.io/crates/v/hc-256.svg)](https://crates.io/crates/hc-256) | [![Documentation](https://docs.rs/hc-256/badge.svg)](https://docs.rs/hc-256) | ![MSRV 1.56][msrv-1.56] | +| [Rabbit] | [`rabbit`] | [![crates.io](https://img.shields.io/crates/v/rabbit.svg)](https://crates.io/crates/rabbit) | [![Documentation](https://docs.rs/rabbit/badge.svg)](https://docs.rs/rabbit) | ![MSRV 1.56][msrv-1.56] | +| [Salsa20] | [`salsa20`] | [![crates.io](https://img.shields.io/crates/v/salsa20.svg)](https://crates.io/crates/salsa20) | [![Documentation](https://docs.rs/salsa20/badge.svg)](https://docs.rs/salsa20) | ![MSRV 1.56][msrv-1.56] | -## MSRV Policy +### Minimum Supported Rust Version (MSRV) Policy -Minimum Supported Rust Version (MSRV) can be changed in the future, but it will be -done with a minor version bump. +MSRV bump is considered a breaking change and will be performed only with a minor version bump. -## Usage +## Example -Crates functionality is expressed in terms of traits defined in the [`cipher`][2] crate. +Crates functionality is expressed in terms of traits defined in the [`cipher`] crate. -Let's use AES-128-OFB to demonstrate usage of synchronous stream cipher: +Let's use ChaCha20 to demonstrate usage of synchronous stream cipher: ```rust -use aes::Aes128; -use ofb::Ofb; - -// import relevant traits -use ofb::cipher::{NewStreamCipher, SyncStreamCipher}; +use chacha20::ChaCha20; +// Import relevant traits +use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +use hex_literal::hex; -// OFB mode implementation is generic over block ciphers -// we will create a type alias for convenience -type AesOfb = Ofb; +let key = [0x42; 32]; +let nonce = [0x24; 12]; +let plaintext = hex!("00010203 04050607 08090a0b 0c0d0e0f"); +let ciphertext = hex!("e405626e 4f1236b3 670ee428 332ea20e"); -let key = b"very secret key."; -let iv = b"unique init vect"; -let plaintext = b"The quick brown fox jumps over the lazy dog."; +// Key and IV must be references to the `GenericArray` type. +// Here we use the `Into` trait to convert arrays into it. +let mut cipher = ChaCha20::new(&key.into(), &nonce.into()); -let mut buffer = plaintext.to_vec(); - -// create cipher instance -let mut cipher = AesOfb::new_var(key, iv)?; +let mut buffer = plaintext.clone(); // apply keystream (encrypt) cipher.apply_keystream(&mut buffer); +assert_eq!(buffer, ciphertext); + +let ciphertext = buffer.clone(); -// and decrypt it back -AesOfb::new_var(key, iv)?.apply_keystream(&mut buffer); +// ChaCha ciphers support seeking +cipher.seek(0u32); + +// decrypt ciphertext by applying keystream again +cipher.apply_keystream(&mut buffer); +assert_eq!(buffer, plaintext); // stream ciphers can be used with streaming messages -let mut cipher = AesOfb::new_var(key, iv).unwrap(); +cipher.seek(0u32); for chunk in buffer.chunks_mut(3) { cipher.apply_keystream(chunk); } +assert_eq!(buffer, ciphertext); ``` ## License @@ -80,9 +85,7 @@ at your option. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) @@ -90,22 +93,26 @@ dual licensed as above, without any additional terms or conditions. [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers [deps-image]: https://deps.rs/repo/github/RustCrypto/stream-ciphers/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/stream-ciphers +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg [hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md +[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg [//]: # (footnotes) -[1]: https://en.wikipedia.org/wiki/Stream_cipher -[2]: https://docs.rs/cipher +[stream ciphers]: https://en.wikipedia.org/wiki/Stream_cipher +[`cipher`]: https://docs.rs/cipher [//]: # (crates) -[`cfb-mode`]: https://github.com/RustCrypto/stream-ciphers/tree/master/cfb-mode -[`cfb8`]: https://github.com/RustCrypto/stream-ciphers/tree/master/cfb8 -[`chacha20`]: https://github.com/RustCrypto/stream-ciphers/tree/master/chacha20 -[`ctr`]: https://github.com/RustCrypto/stream-ciphers/tree/master/ctr -[`hc-256`]: https://github.com/RustCrypto/stream-ciphers/tree/master/hc-256 -[`ofb`]: https://github.com/RustCrypto/stream-ciphers/tree/master/ofb -[`rabbit`]: https://github.com/RustCrypto/stream-ciphers/tree/master/rabbit -[`salsa20`]: https://github.com/RustCrypto/stream-ciphers/tree/master/salsa20 +[`chacha20`]: ./chacha20 +[`hc-256`]: ./hc-256 +[`rabbit`]: ./rabbit +[`salsa20`]: ./salsa20 + +[//]: # (links) +[ChaCha]: https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant +[HC-256]: https://en.wikipedia.org/wiki/HC-256 +[Rabbit]: https://en.wikipedia.org/wiki/Rabbit_(cipher) +[Salsa20]: https://en.wikipedia.org/wiki/Salsa20 diff --git a/benches/Cargo.toml b/benches/Cargo.toml index d316eee3..aba16fbb 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -12,7 +12,7 @@ publish = false [dev-dependencies] criterion = "0.3" criterion-cycles-per-byte = "0.1" -chacha20 = { path = "../chacha20/", features = ["cipher"] } +chacha20 = { path = "../chacha20/" } [[bench]] name = "chacha20" diff --git a/benches/src/chacha20.rs b/benches/src/chacha20.rs index 1b44e488..6b772af2 100644 --- a/benches/src/chacha20.rs +++ b/benches/src/chacha20.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Through use criterion_cycles_per_byte::CyclesPerByte; use chacha20::{ - cipher::{NewCipher, StreamCipher}, + cipher::{KeyIvInit, StreamCipher}, ChaCha20, }; @@ -21,7 +21,6 @@ fn bench(c: &mut Criterion) { let key = Default::default(); let nonce = Default::default(); let mut cipher = ChaCha20::new(&key, &nonce); - b.iter(|| cipher.apply_keystream(&mut buf)); }); } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index e69de29b..8b137891 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -0,0 +1 @@ + diff --git a/cfb-mode/CHANGELOG.md b/cfb-mode/CHANGELOG.md deleted file mode 100644 index 403f72ad..00000000 --- a/cfb-mode/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.7.1 (2021-04-30) -### Changed -- Removed redundant `NewBlockCipher` bound from `FromBlockCipher` implementation ([#236]) - -[#236]: https://github.com/RustCrypto/stream-ciphers/pull/236 - -## 0.7.0 (2021-04-29) -### Changed -- Bump `cipher` crate dependency to v0.3 release ([#226]) -- Bump `aes` dev dependency to v0.7 release ([#232]) - -[#226]: https://github.com/RustCrypto/stream-ciphers/pull/226 -[#232]: https://github.com/RustCrypto/stream-ciphers/pull/232 - -## 0.6.0 (2020-10-16) -### Changed -- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177]) - -[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177 - -## 0.5.0 (2020-08-25) -### Changed -- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164]) - -[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161 -[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164 - -## 0.4.0 (2020-06-08) -### Changed -- Bump `stream-cipher` dependency to v0.4 ([#119]) -- Upgrade to Rust 2018 edition ([#119]) - -[#119]: https://github.com/RustCrypto/stream-ciphers/pull/119 - -## 0.3.2 (2019-03-11) - -## 0.3.1 (2018-11-01) - -## 0.3.0 (2018-11-01) - -## 0.2.0 (2018-10-13) - -## 0.1.0 (2018-10-01) diff --git a/cfb-mode/Cargo.toml b/cfb-mode/Cargo.toml deleted file mode 100644 index 7b3508e9..00000000 --- a/cfb-mode/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "cfb-mode" -version = "0.7.1" # Also update html_root_url in lib.rs when bumping this -description = "Generic Cipher Feedback (CFB) mode implementation." -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -documentation = "https://docs.rs/cfb-mode" -repository = "https://github.com/RustCrypto/stream-ciphers" -keywords = ["crypto", "stream-cipher", "block-mode"] -categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" - -[dependencies] -cipher = "0.3" - -[dev-dependencies] -aes = { version = "0.7", features = ["force-soft"] } # Uses `force-soft` for MSRV 1.41 -cipher = { version = "0.3", features = ["dev"] } -hex-literal = "0.2" diff --git a/cfb-mode/LICENSE-APACHE b/cfb-mode/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/cfb-mode/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/cfb-mode/LICENSE-MIT b/cfb-mode/LICENSE-MIT deleted file mode 100644 index f5b157a6..00000000 --- a/cfb-mode/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 Artyom Pavlov - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/cfb-mode/README.md b/cfb-mode/README.md deleted file mode 100644 index 3bf121f2..00000000 --- a/cfb-mode/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# RustCrypto: Cipher Feedback Mode (CFB) - -[![Crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] -[![HAZMAT][hazmat-image]][hazmat-link] - -Generic [Cipher Feedback (CFB)][1] mode implementation as a -[self-synchronizing stream cipher][2]. - -[Documentation][docs-link] - -diagram - -## ⚠️ Security Warning: [Hazmat!][hazmat-link] - -This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to -verify ciphertext integrity), which can lead to serious vulnerabilities -if used incorrectly! - -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. - -USE AT YOUR OWN RISK! - -## Minimum Supported Rust Version - -Rust **1.41** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/cfb-mode.svg -[crate-link]: https://crates.io/crates/cfb-mode -[docs-image]: https://docs.rs/cfb-mode/badge.svg -[docs-link]: https://docs.rs/cfb-mode/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers -[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg -[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md -[build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/cfb-mode/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/stream-ciphers/actions?query=workflow%3Acfb-mode - -[//]: # (footnotes) - -[1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB -[2]: https://en.wikipedia.org/wiki/Stream_cipher#Self-synchronizing_stream_ciphers diff --git a/cfb-mode/benches/cfb-mode.rs b/cfb-mode/benches/cfb-mode.rs deleted file mode 100644 index cd6c7aba..00000000 --- a/cfb-mode/benches/cfb-mode.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_async_bench!(cfb_mode::Cfb); diff --git a/cfb-mode/src/lib.rs b/cfb-mode/src/lib.rs deleted file mode 100644 index 22ed9033..00000000 --- a/cfb-mode/src/lib.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Generic [Cipher Feedback (CFB)][1] mode implementation. -//! -//! This crate implements CFB as a [self-synchronizing stream cipher][2]. -//! -//! # Security Warning -//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity -//! is not verified, which can lead to serious vulnerabilities! -//! -//! # Examples -//! ``` -//! use aes::Aes128; -//! use cfb_mode::Cfb; -//! use cfb_mode::cipher::{NewCipher, AsyncStreamCipher}; -//! use hex_literal::hex; -//! -//! type AesCfb = Cfb; -//! -//! let key = b"very secret key."; -//! let iv = b"unique init vect"; -//! let plaintext = b"The quick brown fox jumps over the lazy dog."; -//! let ciphertext = hex!(" -//! 8f0cb6e8 9286cd02 09c95da4 fa663269 -//! bf7f286d 76820a4a f6cd3794 64cb6765 -//! 8c764fa2 ce107f96 e1cf1dcd -//! "); -//! -//! let mut data = plaintext.to_vec(); -//! // encrypt plaintext -//! AesCfb::new_from_slices(key, iv).unwrap().encrypt(&mut data); -//! assert_eq!(data, &ciphertext[..]); -//! // and decrypt it back -//! AesCfb::new_from_slices(key, iv).unwrap().decrypt(&mut data); -//! assert_eq!(data, &plaintext[..]); -//! -//! // CFB mode can be used with streaming messages -//! let mut cipher = AesCfb::new_from_slices(key, iv).unwrap(); -//! for chunk in data.chunks_mut(3) { -//! cipher.encrypt(chunk); -//! } -//! assert_eq!(data, &ciphertext[..]); -//! ``` -//! -//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB -//! [2]: https://en.wikipedia.org/wiki/Stream_cipher#Self-synchronizing_stream_ciphers - -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/cfb-mode/0.7.1" -)] -#![warn(missing_docs, rust_2018_idioms)] - -pub use cipher; - -use cipher::{ - generic_array::{typenum::Unsigned, GenericArray}, - AsyncStreamCipher, BlockCipher, BlockEncrypt, FromBlockCipher, ParBlocks, -}; -use core::slice; - -/// CFB self-synchronizing stream cipher instance. -pub struct Cfb { - cipher: C, - iv: GenericArray, - pos: usize, -} - -impl FromBlockCipher for Cfb { - type BlockCipher = C; - type NonceSize = C::BlockSize; - - fn from_block_cipher(cipher: C, iv: &GenericArray) -> Self { - let mut iv = iv.clone(); - cipher.encrypt_block(&mut iv); - Self { cipher, iv, pos: 0 } - } -} - -impl AsyncStreamCipher for Cfb { - fn encrypt(&mut self, mut data: &mut [u8]) { - let bs = C::BlockSize::USIZE; - let n = data.len(); - - if n < bs - self.pos { - xor_set1(data, &mut self.iv[self.pos..self.pos + n]); - self.pos += n; - return; - } - - let (left, right) = { data }.split_at_mut(bs - self.pos); - data = right; - let mut iv = self.iv.clone(); - xor_set1(left, &mut iv[self.pos..]); - self.cipher.encrypt_block(&mut iv); - - let mut chunks = data.chunks_exact_mut(bs); - for chunk in &mut chunks { - xor_set1(chunk, iv.as_mut_slice()); - self.cipher.encrypt_block(&mut iv); - } - - let rem = chunks.into_remainder(); - xor_set1(rem, iv.as_mut_slice()); - self.pos = rem.len(); - self.iv = iv; - } - - fn decrypt(&mut self, mut data: &mut [u8]) { - let bs = C::BlockSize::to_usize(); - let pb = C::ParBlocks::to_usize(); - let n = data.len(); - - if n < bs - self.pos { - xor_set2(data, &mut self.iv[self.pos..self.pos + n]); - self.pos += n; - return; - } - let (left, right) = { data }.split_at_mut(bs - self.pos); - data = right; - let mut iv = self.iv.clone(); - xor_set2(left, &mut iv[self.pos..]); - self.cipher.encrypt_block(&mut iv); - - let bss = bs * pb; - if pb != 1 && data.len() >= bss { - let mut iv_blocks: ParBlocks = - unsafe { (&*(data.as_ptr() as *const ParBlocks)).clone() }; - self.cipher.encrypt_blocks(&mut iv_blocks); - let (block, r) = { data }.split_at_mut(bs); - data = r; - xor(block, iv.as_slice()); - - while data.len() >= 2 * bss - bs { - let (blocks, r) = { data }.split_at_mut(bss); - data = r; - let mut next_iv_blocks: ParBlocks = unsafe { - let ptr = data.as_ptr().offset(-(bs as isize)); - (&*(ptr as *const ParBlocks)).clone() - }; - self.cipher.encrypt_blocks(&mut next_iv_blocks); - - xor(blocks, unsafe { - let ptr = iv_blocks.as_mut_ptr() as *mut u8; - slice::from_raw_parts(ptr, bss) - }); - iv_blocks = next_iv_blocks; - } - - let n = pb - 1; - let (blocks, r) = { data }.split_at_mut(n * bs); - data = r; - let chunks = blocks.chunks_mut(bs); - for (iv, block) in iv_blocks[..n].iter().zip(chunks) { - xor(block, iv.as_slice()) - } - iv = iv_blocks[n].clone(); - } - - let mut chunks = data.chunks_exact_mut(bs); - for chunk in &mut chunks { - xor_set2(chunk, iv.as_mut_slice()); - self.cipher.encrypt_block(&mut iv); - } - - let rem = chunks.into_remainder(); - xor_set2(rem, iv.as_mut_slice()); - self.pos = rem.len(); - self.iv = iv; - } -} - -#[inline(always)] -fn xor(buf1: &mut [u8], buf2: &[u8]) { - for (a, b) in buf1.iter_mut().zip(buf2) { - *a ^= *b; - } -} - -#[inline(always)] -fn xor_set1(buf1: &mut [u8], buf2: &mut [u8]) { - for (a, b) in buf1.iter_mut().zip(buf2) { - let t = *a ^ *b; - *a = t; - *b = t; - } -} - -#[inline(always)] -fn xor_set2(buf1: &mut [u8], buf2: &mut [u8]) { - for (a, b) in buf1.iter_mut().zip(buf2) { - let t = *a; - *a ^= *b; - *b = t; - } -} diff --git a/cfb-mode/tests/data/aes128.blb b/cfb-mode/tests/data/aes128.blb deleted file mode 100644 index 69237462..00000000 Binary files a/cfb-mode/tests/data/aes128.blb and /dev/null differ diff --git a/cfb-mode/tests/data/aes192.blb b/cfb-mode/tests/data/aes192.blb deleted file mode 100644 index 7d9ddf6c..00000000 Binary files a/cfb-mode/tests/data/aes192.blb and /dev/null differ diff --git a/cfb-mode/tests/data/aes256.blb b/cfb-mode/tests/data/aes256.blb deleted file mode 100644 index 767eb025..00000000 Binary files a/cfb-mode/tests/data/aes256.blb and /dev/null differ diff --git a/cfb-mode/tests/lib.rs b/cfb-mode/tests/lib.rs deleted file mode 100644 index 32862eb4..00000000 --- a/cfb-mode/tests/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cfb_mode::Cfb; - -// tests vectors are from: -// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf -cipher::stream_cipher_async_test!(cfb_aes128, "aes128", Cfb); -cipher::stream_cipher_async_test!(cfb_aes192, "aes192", Cfb); -cipher::stream_cipher_async_test!(cfb_aes256, "aes256", Cfb); diff --git a/cfb8/CHANGELOG.md b/cfb8/CHANGELOG.md deleted file mode 100644 index cd56dae2..00000000 --- a/cfb8/CHANGELOG.md +++ /dev/null @@ -1,48 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.7.1 (2021-04-30) -### Changed -- Removed redundant `NewBlockCipher` boundfrom `FromBlockCipher` implementation ([#236]) - -[#236]: https://github.com/RustCrypto/stream-ciphers/pull/236 - -## 0.7.0 (2021-04-29) -### Changed -- Bump `cipher` crate dependency to v0.3 release ([#226]) -- Bump `aes` dev dependency to v0.7 release ([#232]) - -[#226]: https://github.com/RustCrypto/stream-ciphers/pull/226 -[#232]: https://github.com/RustCrypto/stream-ciphers/pull/232 - -## 0.6.0 (2020-10-16) -### Changed -- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177]) - -[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177 - -## 0.5.0 (2020-08-25) -### Changed -- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164]) - -[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161 -[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164 - -## 0.4.0 (2020-06-08) -### Changed -- Bump `stream-cipher` dependency to v0.4 ([#120]) -- Upgrade to Rust 2018 edition ([#120]) - -[#120]: https://github.com/RustCrypto/stream-ciphers/pull/120 - -## 0.3.2 (2019-03-11) - -## 0.3.1 (2018-11-01) - -## 0.3.0 (2018-11-01) - -## 0.1.0 (2018-11-01) diff --git a/cfb8/Cargo.toml b/cfb8/Cargo.toml deleted file mode 100644 index e804d119..00000000 --- a/cfb8/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "cfb8" -version = "0.7.1" # Also update html_root_url in lib.rs when bumping this -description = "Generic 8-bit Cipher Feedback (CFB8) mode implementation." -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -documentation = "https://docs.rs/cfb8" -repository = "https://github.com/RustCrypto/stream-ciphers" -keywords = ["crypto", "stream-cipher", "block-mode"] -categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" - -[dependencies] -cipher = "0.3" - -[dev-dependencies] -aes = { version = "0.7", features = ["force-soft"] } # Uses `force-soft` for MSRV 1.41 -cipher = { version = "0.3", features = ["dev"] } -hex-literal = "0.2" diff --git a/cfb8/LICENSE-APACHE b/cfb8/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/cfb8/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/cfb8/LICENSE-MIT b/cfb8/LICENSE-MIT deleted file mode 100644 index f5b157a6..00000000 --- a/cfb8/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 Artyom Pavlov - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/cfb8/README.md b/cfb8/README.md deleted file mode 100644 index 3c9364a8..00000000 --- a/cfb8/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# RustCrypto: CFB8 - -[![Crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] -[![HAZMAT][hazmat-image]][hazmat-link] - -Generic [8-bit Cipher Feedback (CFB8)][1] mode implementation as a -[self-synchronizing stream cipher][2]. - -[Documentation][docs-link] - -## ⚠️ Security Warning: [Hazmat!][hazmat-link] - -This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to -verify ciphertext integrity), which can lead to serious vulnerabilities -if used incorrectly! - -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. - -USE AT YOUR OWN RISK! - -## Minimum Supported Rust Version - -Rust **1.41** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/cfb8.svg -[crate-link]: https://crates.io/crates/cfb8 -[docs-image]: https://docs.rs/cfb8/badge.svg -[docs-link]: https://docs.rs/cfb8/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers -[build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/cfb8/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/stream-ciphers/actions?query=workflow%3Acfb8 -[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg -[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md - -[//]: # (footnotes) - -[1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB -[2]: https://en.wikipedia.org/wiki/Stream_cipher#Self-synchronizing_stream_ciphers diff --git a/cfb8/benches/cfb8.rs b/cfb8/benches/cfb8.rs deleted file mode 100644 index 721bbaa7..00000000 --- a/cfb8/benches/cfb8.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_async_bench!(cfb8::Cfb8); diff --git a/cfb8/src/lib.rs b/cfb8/src/lib.rs deleted file mode 100644 index b43b86d5..00000000 --- a/cfb8/src/lib.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Generic [8-bit Cipher Feedback (CFB8)][1] mode implementation. -//! -//! This crate implements CFB8 as a [self-synchronizing stream cipher][2]. -//! -//! # Security Warning -//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity -//! is not verified, which can lead to serious vulnerabilities! -//! -//! # Examples -//! ``` -//! use aes::Aes128; -//! use cfb8::Cfb8; -//! use cfb8::cipher::{NewCipher, AsyncStreamCipher}; -//! use hex_literal::hex; -//! -//! type AesCfb8 = Cfb8; -//! -//! let key = b"very secret key."; -//! let iv = b"unique init vect"; -//! let plaintext = b"The quick brown fox jumps over the lazy dog."; -//! let ciphertext = hex!(" -//! 8fb603d8 66a1181c 08506c75 37ee9cad -//! 35be8ff8 e0c79526 9d735d04 c0a93017 -//! b1a748e0 25146b68 23fc9ad3 -//! "); -//! -//! let mut data = plaintext.to_vec(); -//! // encrypt plaintext -//! AesCfb8::new_from_slices(key, iv).unwrap().encrypt(&mut data); -//! assert_eq!(data, &ciphertext[..]); -//! // and decrypt it back -//! AesCfb8::new_from_slices(key, iv).unwrap().decrypt(&mut data); -//! assert_eq!(data, &plaintext[..]); -//! -//! // CFB mode can be used with streaming messages -//! let mut cipher = AesCfb8::new_from_slices(key, iv).unwrap(); -//! for chunk in data.chunks_mut(3) { -//! cipher.encrypt(chunk); -//! } -//! assert_eq!(data, &ciphertext[..]); -//! ``` -//! -//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB -//! [2]: https://en.wikipedia.org/wiki/Stream_cipher#Self-synchronizing_stream_ciphers - -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/cfb8/0.7.1" -)] -#![deny(unsafe_code)] -#![warn(missing_docs, rust_2018_idioms)] - -pub use cipher; - -use cipher::{ - generic_array::GenericArray, AsyncStreamCipher, BlockCipher, BlockEncrypt, FromBlockCipher, -}; - -/// CFB self-synchronizing stream cipher instance. -pub struct Cfb8 { - cipher: C, - iv: GenericArray, -} - -impl FromBlockCipher for Cfb8 { - type BlockCipher = C; - type NonceSize = C::BlockSize; - - fn from_block_cipher(cipher: C, iv: &GenericArray) -> Self { - Self { - cipher, - iv: iv.clone(), - } - } -} - -impl AsyncStreamCipher for Cfb8 { - fn encrypt(&mut self, data: &mut [u8]) { - let mut iv = self.iv.clone(); - let n = iv.len(); - for b in data.iter_mut() { - let iv_copy = iv.clone(); - self.cipher.encrypt_block(&mut iv); - *b ^= iv[0]; - iv[..n - 1].clone_from_slice(&iv_copy[1..]); - iv[n - 1] = *b; - } - self.iv = iv; - } - - fn decrypt(&mut self, data: &mut [u8]) { - let mut iv = self.iv.clone(); - let n = iv.len(); - for b in data.iter_mut() { - let iv_copy = iv.clone(); - self.cipher.encrypt_block(&mut iv); - let t = *b; - *b ^= iv[0]; - iv[..n - 1].clone_from_slice(&iv_copy[1..]); - iv[n - 1] = t; - } - self.iv = iv; - } -} diff --git a/cfb8/tests/data/aes128.blb b/cfb8/tests/data/aes128.blb deleted file mode 100644 index 68e6a1b1..00000000 Binary files a/cfb8/tests/data/aes128.blb and /dev/null differ diff --git a/cfb8/tests/data/aes192.blb b/cfb8/tests/data/aes192.blb deleted file mode 100644 index c54e0fe4..00000000 Binary files a/cfb8/tests/data/aes192.blb and /dev/null differ diff --git a/cfb8/tests/data/aes256.blb b/cfb8/tests/data/aes256.blb deleted file mode 100644 index d12974f5..00000000 Binary files a/cfb8/tests/data/aes256.blb and /dev/null differ diff --git a/cfb8/tests/lib.rs b/cfb8/tests/lib.rs deleted file mode 100644 index 04d846b6..00000000 --- a/cfb8/tests/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cfb8::Cfb8; - -// tests vectors are from: -// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf -cipher::stream_cipher_async_test!(cfb8_aes128, "aes128", Cfb8); -cipher::stream_cipher_async_test!(cfb8_aes192, "aes192", Cfb8); -cipher::stream_cipher_async_test!(cfb8_aes256, "aes256", Cfb8); diff --git a/chacha20/CHANGELOG.md b/chacha20/CHANGELOG.md index 7ad92f9a..4ca6224b 100644 --- a/chacha20/CHANGELOG.md +++ b/chacha20/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.9.0 (2022-02-10) +### Changed +- Bump `cipher` dependency to v0.4 ([#276]) + +[#276]: https://github.com/RustCrypto/stream-ciphers/pull/276 + ## 0.8.1 (2021-08-30) ### Added - NEON implementation for aarch64 ([#274]) diff --git a/chacha20/Cargo.lock b/chacha20/Cargo.lock deleted file mode 100644 index b822913b..00000000 --- a/chacha20/Cargo.lock +++ /dev/null @@ -1,111 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "blobby" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc52553543ecb104069b0ff9e0fcc5c739ad16202935528a112d974e8f1a4ee8" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chacha20" -version = "0.8.1" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "hex-literal", - "rand_core", - "zeroize", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "blobby", - "generic-array", -] - -[[package]] -name = "cpufeatures" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] - -[[package]] -name = "generic-array" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hex-literal" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" -dependencies = [ - "hex-literal-impl", - "proc-macro-hack", -] - -[[package]] -name = "hex-literal-impl" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853f769599eb31de176303197b7ba4973299c38c7a7604a6bc88c3eef05b9b46" -dependencies = [ - "proc-macro-hack", -] - -[[package]] -name = "libc" -version = "0.2.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" - -[[package]] -name = "typenum" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "zeroize" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" diff --git a/chacha20/Cargo.toml b/chacha20/Cargo.toml index 13ddafc2..1f015ecb 100644 --- a/chacha20/Cargo.toml +++ b/chacha20/Cargo.toml @@ -1,8 +1,6 @@ [package] name = "chacha20" -version = "0.8.1" # Also update html_root_url in lib.rs when bumping this -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" +version = "0.9.0" # Also update html_root_url in lib.rs when bumping this description = """ The ChaCha20 stream cipher (RFC 8439) implemented in pure Rust using traits from the RustCrypto `cipher` crate, with optional architecture-specific @@ -10,37 +8,32 @@ hardware acceleration (AVX2, SSE2). Additionally provides the ChaCha8, ChaCha12, XChaCha20, XChaCha12 and XChaCha8 stream ciphers, and also optional rand_core-compatible RNGs based on those ciphers. """ +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/chacha20" repository = "https://github.com/RustCrypto/stream-ciphers" keywords = ["crypto", "stream-cipher", "chacha8", "chacha12", "xchacha20"] categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" [dependencies] cfg-if = "1" - -# optional dependencies -cipher = { version = "0.3", optional = true } -rand_core = { version = "0.6", optional = true, default-features = false } -zeroize = { version = ">=1, <1.5", optional = true, default-features = false } +cipher = "0.4" [target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies] cpufeatures = "0.2" [dev-dependencies] -cipher = { version = "0.3", features = ["dev"] } -hex-literal = "0.2" +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3" [features] -default = ["cipher"] -expose-core = [] -force-soft = [] -hchacha = ["cipher"] -legacy = ["cipher"] -neon = [] -rng = ["rand_core"] std = ["cipher/std"] +zeroize = ["cipher/zeroize"] +neon = [] [package.metadata.docs.rs] -features = ["legacy", "rng", "std"] +all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/chacha20/README.md b/chacha20/README.md index 4137c950..a4450852 100644 --- a/chacha20/README.md +++ b/chacha20/README.md @@ -62,7 +62,7 @@ stream cipher itself) are designed to execute in constant time. ## Minimum Supported Rust Version -Rust **1.51** or higher. +Rust **1.56** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -94,7 +94,7 @@ dual licensed as above, without any additional terms or conditions. [docs-image]: https://docs.rs/chacha20/badge.svg [docs-link]: https://docs.rs/chacha20/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers [build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/chacha20/badge.svg?branch=master&event=push diff --git a/chacha20/benches/chacha12.rs b/chacha20/benches/chacha12.rs deleted file mode 100644 index 089a95d4..00000000 --- a/chacha20/benches/chacha12.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![cfg(feature = "cipher")] -#![feature(test)] - -cipher::stream_cipher_sync_bench!(chacha20::ChaCha12); diff --git a/chacha20/benches/chacha20.rs b/chacha20/benches/chacha20.rs deleted file mode 100644 index cb495736..00000000 --- a/chacha20/benches/chacha20.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![cfg(feature = "cipher")] -#![feature(test)] - -cipher::stream_cipher_sync_bench!(chacha20::ChaCha20); diff --git a/chacha20/benches/chacha8.rs b/chacha20/benches/chacha8.rs deleted file mode 100644 index b621e1cd..00000000 --- a/chacha20/benches/chacha8.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![cfg(feature = "cipher")] -#![feature(test)] - -cipher::stream_cipher_sync_bench!(chacha20::ChaCha8); diff --git a/chacha20/benches/mod.rs b/chacha20/benches/mod.rs new file mode 100644 index 00000000..77588676 --- /dev/null +++ b/chacha20/benches/mod.rs @@ -0,0 +1,26 @@ +#![feature(test)] +extern crate test; + +cipher::stream_cipher_bench!( + chacha20::ChaCha8; + chacha8_bench1_16b 16; + chacha8_bench2_256b 256; + chacha8_bench3_1kib 1024; + chacha8_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + chacha20::ChaCha12; + chacha12_bench1_16b 16; + chacha12_bench2_256b 256; + chacha12_bench3_1kib 1024; + chacha12_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + chacha20::ChaCha20; + chacha20_bench1_16b 16; + chacha20_bench2_256b 256; + chacha20_bench3_1kib 1024; + chacha20_bench4_16kib 16384; +); diff --git a/chacha20/src/backend/soft.rs b/chacha20/src/backend/soft.rs deleted file mode 100644 index 01b3ddb1..00000000 --- a/chacha20/src/backend/soft.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! The ChaCha20 core function. Defined in RFC 8439 Section 2.3. -//! -//! -//! -//! Portable implementation which does not rely on architecture-specific -//! intrinsics. - -use crate::{rounds::Rounds, BLOCK_SIZE, CONSTANTS, IV_SIZE, KEY_SIZE}; -use core::{convert::TryInto, marker::PhantomData}; - -/// Size of buffers passed to `generate` and `apply_keystream` for this backend -#[allow(dead_code)] -pub(crate) const BUFFER_SIZE: usize = BLOCK_SIZE; - -/// Number of 32-bit words in the ChaCha20 state -const STATE_WORDS: usize = 16; - -/// The ChaCha20 core function. -// TODO(tarcieri): zeroize? -#[derive(Clone)] -#[allow(dead_code)] -pub struct Core { - /// Internal state of the core function - state: [u32; STATE_WORDS], - - /// Number of rounds to perform - rounds: PhantomData, -} - -#[allow(dead_code)] -impl Core { - /// Initialize core function with the given key, IV, and number of rounds - pub fn new(key: &[u8; KEY_SIZE], iv: [u8; IV_SIZE]) -> Self { - let state = [ - CONSTANTS[0], - CONSTANTS[1], - CONSTANTS[2], - CONSTANTS[3], - u32::from_le_bytes(key[..4].try_into().unwrap()), - u32::from_le_bytes(key[4..8].try_into().unwrap()), - u32::from_le_bytes(key[8..12].try_into().unwrap()), - u32::from_le_bytes(key[12..16].try_into().unwrap()), - u32::from_le_bytes(key[16..20].try_into().unwrap()), - u32::from_le_bytes(key[20..24].try_into().unwrap()), - u32::from_le_bytes(key[24..28].try_into().unwrap()), - u32::from_le_bytes(key[28..32].try_into().unwrap()), - 0, - 0, - u32::from_le_bytes(iv[0..4].try_into().unwrap()), - u32::from_le_bytes(iv[4..].try_into().unwrap()), - ]; - - Self { - state, - rounds: PhantomData, - } - } - - /// Generate output, overwriting data already in the buffer - #[inline] - pub fn generate(&mut self, counter: u64, output: &mut [u8]) { - debug_assert_eq!(output.len(), BUFFER_SIZE); - self.counter_setup(counter); - - let mut state = self.state; - self.rounds(&mut state); - - for (i, chunk) in output.chunks_mut(4).enumerate() { - chunk.copy_from_slice(&state[i].to_le_bytes()); - } - } - - /// Apply generated keystream to the output buffer - #[inline] - #[cfg(feature = "cipher")] - pub fn apply_keystream(&mut self, counter: u64, output: &mut [u8]) { - debug_assert_eq!(output.len(), BUFFER_SIZE); - self.counter_setup(counter); - - let mut state = self.state; - self.rounds(&mut state); - - for (i, chunk) in output.chunks_mut(4).enumerate() { - for (a, b) in chunk.iter_mut().zip(&state[i].to_le_bytes()) { - *a ^= *b; - } - } - } - - #[inline] - fn counter_setup(&mut self, counter: u64) { - self.state[12] = (counter & 0xffff_ffff) as u32; - self.state[13] = ((counter >> 32) & 0xffff_ffff) as u32; - } - - #[inline] - fn rounds(&mut self, state: &mut [u32; STATE_WORDS]) { - for _ in 0..(R::COUNT / 2) { - // column rounds - quarter_round(0, 4, 8, 12, state); - quarter_round(1, 5, 9, 13, state); - quarter_round(2, 6, 10, 14, state); - quarter_round(3, 7, 11, 15, state); - - // diagonal rounds - quarter_round(0, 5, 10, 15, state); - quarter_round(1, 6, 11, 12, state); - quarter_round(2, 7, 8, 13, state); - quarter_round(3, 4, 9, 14, state); - } - - for (s1, s0) in state.iter_mut().zip(&self.state) { - *s1 = s1.wrapping_add(*s0); - } - } -} - -/// The ChaCha20 quarter round function -#[inline] -pub(crate) fn quarter_round( - a: usize, - b: usize, - c: usize, - d: usize, - state: &mut [u32; STATE_WORDS], -) { - state[a] = state[a].wrapping_add(state[b]); - state[d] ^= state[a]; - state[d] = state[d].rotate_left(16); - - state[c] = state[c].wrapping_add(state[d]); - state[b] ^= state[c]; - state[b] = state[b].rotate_left(12); - - state[a] = state[a].wrapping_add(state[b]); - state[d] ^= state[a]; - state[d] = state[d].rotate_left(8); - - state[c] = state[c].wrapping_add(state[d]); - state[b] ^= state[c]; - state[b] = state[b].rotate_left(7); -} diff --git a/chacha20/src/backend.rs b/chacha20/src/backends.rs similarity index 96% rename from chacha20/src/backend.rs rename to chacha20/src/backends.rs index 9d349f0d..a6669116 100644 --- a/chacha20/src/backend.rs +++ b/chacha20/src/backends.rs @@ -3,10 +3,10 @@ //! Defined in RFC 8439 Section 2.3: //! -use cfg_if::cfg_if; +// use cfg_if::cfg_if; pub(crate) mod soft; - +/* cfg_if! { if #[cfg(all( any(target_arch = "x86", target_arch = "x86_64"), @@ -33,3 +33,4 @@ cfg_if! { pub use self::soft::Core; } } +*/ diff --git a/chacha20/src/backend/autodetect.rs b/chacha20/src/backends/autodetect.rs similarity index 100% rename from chacha20/src/backend/autodetect.rs rename to chacha20/src/backends/autodetect.rs diff --git a/chacha20/src/backend/avx2.rs b/chacha20/src/backends/avx2.rs similarity index 99% rename from chacha20/src/backend/avx2.rs rename to chacha20/src/backends/avx2.rs index d31daad2..2c316df6 100644 --- a/chacha20/src/backend/avx2.rs +++ b/chacha20/src/backends/avx2.rs @@ -10,7 +10,7 @@ use super::autodetect::BUFFER_SIZE; use crate::{rounds::Rounds, BLOCK_SIZE, CONSTANTS, IV_SIZE, KEY_SIZE}; -use core::{convert::TryInto, marker::PhantomData}; +use core::marker::PhantomData; #[cfg(target_arch = "x86")] use core::arch::x86::*; diff --git a/chacha20/src/backend/neon.rs b/chacha20/src/backends/neon.rs similarity index 99% rename from chacha20/src/backend/neon.rs rename to chacha20/src/backends/neon.rs index b4759e0f..4524b2ca 100644 --- a/chacha20/src/backend/neon.rs +++ b/chacha20/src/backends/neon.rs @@ -6,7 +6,7 @@ //! backend (public domain). use crate::{rounds::Rounds, BLOCK_SIZE, CONSTANTS, IV_SIZE, KEY_SIZE}; -use core::{convert::TryInto, marker::PhantomData}; +use core::marker::PhantomData; /// Size of buffers passed to `generate` and `apply_keystream` for this backend, which /// operates on four blocks in parallel for optimal performance. @@ -501,7 +501,6 @@ mod tests { use super::*; use crate::rounds::R20; use crate::{backend::soft, BLOCK_SIZE}; - use core::convert::TryInto; // random inputs for testing const R_CNT: u64 = 0x9fe625b6d23a8fa8u64; diff --git a/chacha20/src/backends/soft.rs b/chacha20/src/backends/soft.rs new file mode 100644 index 00000000..0029fcb2 --- /dev/null +++ b/chacha20/src/backends/soft.rs @@ -0,0 +1,80 @@ +//! Portable implementation which does not rely on architecture-specific +//! intrinsics. + +use crate::{Block, ChaChaCore, Unsigned, STATE_WORDS}; +use cipher::{ + consts::{U1, U64}, + BlockSizeUser, ParBlocksSizeUser, StreamBackend, +}; + +pub(crate) struct Backend<'a, R: Unsigned>(pub(crate) &'a mut ChaChaCore); + +impl<'a, R: Unsigned> BlockSizeUser for Backend<'a, R> { + type BlockSize = U64; +} + +impl<'a, R: Unsigned> ParBlocksSizeUser for Backend<'a, R> { + type ParBlocksSize = U1; +} + +impl<'a, R: Unsigned> StreamBackend for Backend<'a, R> { + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + let res = run_rounds::(&self.0.state); + self.0.state[12] = self.0.state[12].wrapping_add(1); + + for (chunk, val) in block.chunks_exact_mut(4).zip(res.iter()) { + chunk.copy_from_slice(&val.to_le_bytes()); + } + } +} + +#[inline(always)] +fn run_rounds(state: &[u32; STATE_WORDS]) -> [u32; STATE_WORDS] { + let mut res = *state; + + for _ in 0..R::USIZE { + // column rounds + quarter_round(0, 4, 8, 12, &mut res); + quarter_round(1, 5, 9, 13, &mut res); + quarter_round(2, 6, 10, 14, &mut res); + quarter_round(3, 7, 11, 15, &mut res); + + // diagonal rounds + quarter_round(0, 5, 10, 15, &mut res); + quarter_round(1, 6, 11, 12, &mut res); + quarter_round(2, 7, 8, 13, &mut res); + quarter_round(3, 4, 9, 14, &mut res); + } + + for (s1, s0) in res.iter_mut().zip(state.iter()) { + *s1 = s1.wrapping_add(*s0); + } + res +} + +/// The ChaCha20 quarter round function +#[inline] +pub(crate) fn quarter_round( + a: usize, + b: usize, + c: usize, + d: usize, + state: &mut [u32; STATE_WORDS], +) { + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(16); + + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(12); + + state[a] = state[a].wrapping_add(state[b]); + state[d] ^= state[a]; + state[d] = state[d].rotate_left(8); + + state[c] = state[c].wrapping_add(state[d]); + state[b] ^= state[c]; + state[b] = state[b].rotate_left(7); +} diff --git a/chacha20/src/backend/sse2.rs b/chacha20/src/backends/sse2.rs similarity index 99% rename from chacha20/src/backend/sse2.rs rename to chacha20/src/backends/sse2.rs index 8d63e1c8..d49b7380 100644 --- a/chacha20/src/backend/sse2.rs +++ b/chacha20/src/backends/sse2.rs @@ -6,7 +6,7 @@ use super::autodetect::BUFFER_SIZE; use crate::{rounds::Rounds, BLOCK_SIZE, CONSTANTS, IV_SIZE, KEY_SIZE}; -use core::{convert::TryInto, marker::PhantomData}; +use core::marker::PhantomData; #[cfg(target_arch = "x86")] use core::arch::x86::*; @@ -239,7 +239,6 @@ mod tests { use super::*; use crate::rounds::R20; use crate::{backend::soft, BLOCK_SIZE}; - use core::convert::TryInto; // random inputs for testing const R_CNT: u64 = 0x9fe625b6d23a8fa8u64; diff --git a/chacha20/src/chacha.rs b/chacha20/src/chacha.rs deleted file mode 100644 index 3ae29fb4..00000000 --- a/chacha20/src/chacha.rs +++ /dev/null @@ -1,253 +0,0 @@ -//! ChaCha20 stream cipher implementation. -//! -//! Adapted from the `ctr` crate. - -// TODO(tarcieri): figure out how to unify this with the `ctr` crate (see #95) - -use crate::{ - backend::{Core, BUFFER_SIZE}, - max_blocks::{MaxCounter, C32}, - rounds::{Rounds, R12, R20, R8}, - BLOCK_SIZE, -}; -use cipher::{ - consts::{U12, U32}, - errors::{LoopError, OverflowError}, - NewCipher, SeekNum, StreamCipher, StreamCipherSeek, -}; -use core::{ - convert::TryInto, - fmt::{self, Debug}, - marker::PhantomData, -}; - -#[cfg(docsrs)] -use cipher::generic_array::GenericArray; - -/// ChaCha8 stream cipher (reduced-round variant of [`ChaCha20`] with 8 rounds) -pub type ChaCha8 = ChaCha; - -/// ChaCha12 stream cipher (reduced-round variant of [`ChaCha20`] with 12 rounds) -pub type ChaCha12 = ChaCha; - -/// ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce) -pub type ChaCha20 = ChaCha; - -/// ChaCha20 key type (256-bits/32-bytes) -/// -/// Implemented as an alias for [`GenericArray`]. -/// -/// (NOTE: all variants of [`ChaCha20`] including `XChaCha20` use the same key type) -pub type Key = cipher::CipherKey; - -/// Nonce type (96-bits/12-bytes) -/// -/// Implemented as an alias for [`GenericArray`]. -pub type Nonce = cipher::Nonce; - -/// Internal buffer -type Buffer = [u8; BUFFER_SIZE]; - -/// How much to increment the counter by for each buffer we generate. -/// Normally this is 1 but the AVX2 backend uses double-wide buffers. -// TODO(tarcieri): support a parallel blocks count like the `ctr` crate -// See: -const COUNTER_INCR: u64 = (BUFFER_SIZE as u64) / (BLOCK_SIZE as u64); - -/// ChaCha family stream cipher, generic around a number of rounds. -/// -/// Use the [`ChaCha8`], [`ChaCha12`], or [`ChaCha20`] type aliases to select -/// a specific number of rounds. -/// -/// Generally [`ChaCha20`] is preferred. -pub struct ChaCha { - _max_blocks: PhantomData, - - /// ChaCha20 core function initialized with a key and IV - block: Core, - - /// Buffer containing previous output - buffer: Buffer, - - /// Position within buffer, or `None` if the buffer is not in use - buffer_pos: u16, - - /// Current counter value relative to the start of the keystream - counter: u64, - - /// Offset of the initial counter in the keystream. This is derived from - /// the extra 4 bytes in the 96-byte nonce RFC 8439 version (or is always - /// 0 in the legacy version) - counter_offset: u64, -} - -impl NewCipher for ChaCha { - /// Key size in bytes - type KeySize = U32; - - /// Nonce size in bytes - type NonceSize = U12; - - fn new(key: &Key, nonce: &Nonce) -> Self { - let block = Core::new( - key.as_slice().try_into().unwrap(), - nonce[4..12].try_into().unwrap(), - ); - - let counter_offset = (u64::from(nonce[0]) & 0xff) << 32 - | (u64::from(nonce[1]) & 0xff) << 40 - | (u64::from(nonce[2]) & 0xff) << 48 - | (u64::from(nonce[3]) & 0xff) << 56; - - Self { - _max_blocks: PhantomData, - block, - buffer: [0u8; BUFFER_SIZE], - buffer_pos: 0, - counter: 0, - counter_offset, - } - } -} - -impl StreamCipher for ChaCha { - fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { - self.check_data_len(data)?; - let pos = self.buffer_pos as usize; - - let mut counter = self.counter; - // xor with leftover bytes from the last call if any - if pos != 0 { - if data.len() < BUFFER_SIZE - pos { - let n = pos + data.len(); - xor(data, &self.buffer[pos..n]); - self.buffer_pos = n as u16; - return Ok(()); - } else { - let (l, r) = data.split_at_mut(BUFFER_SIZE - pos); - data = r; - if let Some(new_ctr) = counter.checked_add(COUNTER_INCR) { - counter = new_ctr; - } else if data.is_empty() { - self.buffer_pos = BUFFER_SIZE as u16; - } else { - return Err(LoopError); - } - xor(l, &self.buffer[pos..]); - } - } - - if self.buffer_pos == BUFFER_SIZE as u16 { - if data.is_empty() { - return Ok(()); - } else { - return Err(LoopError); - } - } - - let mut chunks = data.chunks_exact_mut(BUFFER_SIZE); - for chunk in &mut chunks { - // TODO(tarcieri): double check this should be checked and not wrapping - let counter_with_offset = self.counter_offset.checked_add(counter).unwrap(); - self.block.apply_keystream(counter_with_offset, chunk); - counter = counter.checked_add(COUNTER_INCR).unwrap(); - } - - let rem = chunks.into_remainder(); - self.buffer_pos = rem.len() as u16; - self.counter = counter; - if !rem.is_empty() { - self.generate_block(counter); - xor(rem, &self.buffer[..rem.len()]); - } - - Ok(()) - } -} - -impl StreamCipherSeek for ChaCha { - fn try_current_pos(&self) -> Result { - // quick and dirty fix, until ctr-like parallel block processing will be added - let (counter, pos) = { - let mut counter = self.counter; - let mut pos = self.buffer_pos; - - while pos >= BLOCK_SIZE as u16 { - counter = counter.checked_add(1).ok_or(OverflowError)?; - pos -= BLOCK_SIZE as u16; - } - - (counter, pos) - }; - T::from_block_byte(counter, pos as u8, BLOCK_SIZE as u8) - } - - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - let res: (u64, u8) = pos.to_block_byte(BLOCK_SIZE as u8)?; - let old_counter = self.counter; - let old_buffer_pos = self.buffer_pos; - - self.counter = res.0; - self.buffer_pos = res.1 as u16; - - if let Err(e) = self.check_data_len(&[0]) { - self.counter = old_counter; - self.buffer_pos = old_buffer_pos; - return Err(e); - } - - if self.buffer_pos != 0 { - self.generate_block(self.counter); - } - Ok(()) - } -} - -impl ChaCha { - /// Check data length - fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> { - let buffer_plus_data = (self.buffer_pos as u64) - .checked_add(data.len() as u64) - .ok_or(LoopError)?; - let last_byte_offset = if buffer_plus_data > 0 { - buffer_plus_data - 1 - } else { - 0 - }; - let last_ctr_val = self - .counter - .checked_add(last_byte_offset / (BLOCK_SIZE as u64)) - .ok_or(LoopError)?; - if let Some(mb) = MC::MAX_BLOCKS { - if last_ctr_val <= mb { - Ok(()) - } else { - Err(LoopError) - } - } else { - Ok(()) - } - } - - /// Generate a block, storing it in the internal buffer - #[inline] - fn generate_block(&mut self, counter: u64) { - // TODO(tarcieri): double check this should be checked and not wrapping - let counter_with_offset = self.counter_offset.checked_add(counter).unwrap(); - self.block.generate(counter_with_offset, &mut self.buffer); - } -} - -impl Debug for ChaCha { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "Cipher {{ .. }}") - } -} - -#[inline(always)] -fn xor(buf: &mut [u8], key: &[u8]) { - debug_assert_eq!(buf.len(), key.len()); - for (a, b) in buf.iter_mut().zip(key) { - *a ^= *b; - } -} diff --git a/chacha20/src/legacy.rs b/chacha20/src/legacy.rs index a0324497..2541079c 100644 --- a/chacha20/src/legacy.rs +++ b/chacha20/src/legacy.rs @@ -1,53 +1,76 @@ //! Legacy version of ChaCha20 with a 64-bit nonce -use crate::{ - chacha::{ChaCha, Key}, - max_blocks::C64, - rounds::R20, -}; - +use super::{ChaChaCore, Key, Nonce}; use cipher::{ - consts::{U32, U8}, - errors::{LoopError, OverflowError}, - NewCipher, SeekNum, StreamCipher, StreamCipherSeek, + consts::{U10, U32, U64, U8}, + generic_array::GenericArray, + BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper, + StreamCipherSeekCore, StreamClosure, }; -/// Size of the nonce for the legacy ChaCha20 stream cipher -#[cfg_attr(docsrs, doc(cfg(feature = "legacy")))] -pub type LegacyNonce = cipher::Nonce; +#[cfg(feature = "zeroize")] +use cipher::zeroize::ZeroizeOnDrop; + +/// Nonce type used by [`ChaCha20Legacy`]. +pub type LegacyNonce = GenericArray; /// The ChaCha20 stream cipher (legacy "djb" construction with 64-bit nonce). /// -/// The `legacy` Cargo feature must be enabled to use this. -#[cfg_attr(docsrs, doc(cfg(feature = "legacy")))] -pub struct ChaCha20Legacy(ChaCha); +/// **WARNING:** this implementation uses 32-bit counter, while the original +/// implementation uses 64-bit counter. In other words, it does +/// not allow encrypting of more than 256 GiB of data. +pub type ChaCha20Legacy = StreamCipherCoreWrapper; -impl NewCipher for ChaCha20Legacy { - /// Key size in bytes +/// The ChaCha20 stream cipher (legacy "djb" construction with 64-bit nonce). +pub struct ChaCha20LegacyCore(ChaChaCore); + +impl KeySizeUser for ChaCha20LegacyCore { type KeySize = U32; +} - /// Nonce size in bytes - type NonceSize = U8; +impl IvSizeUser for ChaCha20LegacyCore { + type IvSize = U8; +} - fn new(key: &Key, nonce: &LegacyNonce) -> Self { - let mut exp_iv = [0u8; 12]; - exp_iv[4..].copy_from_slice(nonce); - ChaCha20Legacy(ChaCha::new(key, &exp_iv.into())) +impl BlockSizeUser for ChaCha20LegacyCore { + type BlockSize = U64; +} + +impl KeyIvInit for ChaCha20LegacyCore { + #[inline(always)] + fn new(key: &Key, iv: &LegacyNonce) -> Self { + let mut padded_iv = Nonce::default(); + padded_iv[4..].copy_from_slice(iv); + ChaCha20LegacyCore(ChaChaCore::new(key, &padded_iv)) } } -impl StreamCipher for ChaCha20Legacy { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - self.0.try_apply_keystream(data) +impl StreamCipherCore for ChaCha20LegacyCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + self.0.remaining_blocks() + } + + #[inline(always)] + fn process_with_backend(&mut self, f: impl StreamClosure) { + self.0.process_with_backend(f); } } -impl StreamCipherSeek for ChaCha20Legacy { - fn try_current_pos(&self) -> Result { - self.0.try_current_pos() +impl StreamCipherSeekCore for ChaCha20LegacyCore { + type Counter = u32; + + #[inline(always)] + fn get_block_pos(&self) -> u32 { + self.0.get_block_pos() } - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - self.0.try_seek(pos) + #[inline(always)] + fn set_block_pos(&mut self, pos: u32) { + self.0.set_block_pos(pos); } } + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for ChaCha20LegacyCore {} diff --git a/chacha20/src/lib.rs b/chacha20/src/lib.rs index 98f8d2b5..ba9c87f8 100644 --- a/chacha20/src/lib.rs +++ b/chacha20/src/lib.rs @@ -1,28 +1,29 @@ -//! The ChaCha20 stream cipher ([RFC 8439]) +//! Implementation of the [ChaCha] family of stream ciphers. //! -//! ChaCha20 is a lightweight stream cipher which is amenable to fast, -//! constant-time implementations in software. It improves upon the previous -//! [Salsa20] stream cipher, providing increased per-round diffusion -//! with no cost to performance. +//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate. //! -//! Cipher functionality is accessed using traits from re-exported -//! [`cipher`](https://docs.rs/cipher) crate. +//! ChaCha stream ciphers are lightweight and amenable to fast, constant-time +//! implementations in software. It improves upon the previous [Salsa] design, +//! providing increased per-round diffusion with no cost to performance. //! //! This crate contains the following variants of the ChaCha20 core algorithm: //! //! - [`ChaCha20`]: standard IETF variant with 96-bit nonce -//! - [`ChaCha20Legacy`]: (gated under the `legacy` feature) "djb" variant with 64-bit nonce //! - [`ChaCha8`] / [`ChaCha12`]: reduced round variants of ChaCha20 //! - [`XChaCha20`]: 192-bit extended nonce variant //! - [`XChaCha8`] / [`XChaCha12`]: reduced round variants of XChaCha20 +//! - [`ChaCha20Legacy`]: "djb" variant with 64-bit nonce. +//! **WARNING:** This implementation internally uses 32-bit counter, +//! while the original implementation uses 64-bit coutner. In other words, +//! it does not allow encryption of more than 256 GiB of data. //! -//! # ⚠️ Security Warning: [Hazmat!] +//! # ⚠️ Security Warning: Hazmat! //! //! This crate does not ensure ciphertexts are authentic, which can lead to //! serious vulnerabilities if used incorrectly! //! -//! If in doubt, use the [`chacha20poly1305`](https://docs.rs/chacha20poly1305) -//! crate instead, which provides an authenticated mode on top of ChaCha20. +//! If in doubt, use the [`chacha20poly1305`] crate instead, which provides +//! an authenticated mode on top of ChaCha20. //! //! **USE AT YOUR OWN RISK!** //! @@ -31,7 +32,7 @@ //! This diagram illustrates the ChaCha quarter round function. //! Each round consists of four quarter-rounds: //! -//! +//! //! //! Legend: //! @@ -39,96 +40,180 @@ //! - ‹‹‹ rotate //! - ⊕ xor //! -//! # Usage -//! +//! # Example //! ``` -//! # #[cfg(feature = "cipher")] -//! # { -//! use chacha20::{ChaCha20, Key, Nonce}; -//! use chacha20::cipher::{NewCipher, StreamCipher, StreamCipherSeek}; +//! use chacha20::ChaCha20; +//! // Import relevant traits +//! use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +//! use hex_literal::hex; //! -//! let mut data = [1, 2, 3, 4, 5, 6, 7]; +//! let key = [0x42; 32]; +//! let nonce = [0x24; 12]; +//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F"); +//! let ciphertext = hex!("e405626e 4f1236b3 670ee428 332ea20e"); //! -//! let key = Key::from_slice(b"an example very very secret key."); -//! let nonce = Nonce::from_slice(b"secret nonce"); +//! // Key and IV must be references to the `GenericArray` type. +//! // Here we use the `Into` trait to convert arrays into it. +//! let mut cipher = ChaCha20::new(&key.into(), &nonce.into()); //! -//! // create cipher instance -//! let mut cipher = ChaCha20::new(&key, &nonce); +//! let mut buffer = plaintext.clone(); //! //! // apply keystream (encrypt) -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [73, 98, 234, 202, 73, 143, 0]); -//! -//! // seek to the keystream beginning and apply it again to the `data` (decrypt) -//! cipher.seek(0); -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [1, 2, 3, 4, 5, 6, 7]); -//! # } +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, ciphertext); +//! +//! let ciphertext = buffer.clone(); +//! +//! // ChaCha ciphers support seeking +//! cipher.seek(0u32); +//! +//! // decrypt ciphertext by applying keystream again +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, plaintext); +//! +//! // stream ciphers can be used with streaming messages +//! cipher.seek(0u32); +//! for chunk in buffer.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buffer, ciphertext); //! ``` //! -//! [RFC 8439]: https://tools.ietf.org/html/rfc8439 -//! [Salsa20]: https://docs.rs/salsa20 -//! [Hazmat!]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md +//! [ChaCha]: https://tools.ietf.org/html/rfc8439 +//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20 +//! [`chacha20poly1305`]: https://docs.rs/chacha20poly1305 #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/chacha20/0.8.1" + html_root_url = "https://docs.rs/chacha20/0.9.0" )] -#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr( all(feature = "neon", target_arch = "aarch64", target_feature = "neon"), feature(stdsimd, aarch64_target_feature) )] #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] -mod backend; -#[cfg(feature = "cipher")] -mod chacha; -#[cfg(feature = "legacy")] +pub use cipher; + +use cipher::{ + consts::{U10, U12, U32, U4, U6, U64}, + generic_array::{typenum::Unsigned, GenericArray}, + BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper, + StreamCipherSeekCore, StreamClosure, +}; +use core::marker::PhantomData; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +mod backends; mod legacy; -mod max_blocks; -#[cfg(feature = "rng")] -mod rng; -mod rounds; -#[cfg(feature = "cipher")] mod xchacha; -#[cfg(feature = "cipher")] -pub use cipher; +pub use legacy::{ChaCha20Legacy, ChaCha20LegacyCore, LegacyNonce}; +pub use xchacha::{hchacha, XChaCha12, XChaCha20, XChaCha8, XChaChaCore, XNonce}; -#[cfg(feature = "cipher")] -pub use crate::{ - chacha::{ChaCha, ChaCha12, ChaCha20, ChaCha8, Key, Nonce}, - xchacha::{XChaCha, XChaCha12, XChaCha20, XChaCha8, XNonce}, -}; +/// State initialization constant ("expand 32-byte k") +const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; -#[cfg(feature = "expose-core")] -pub use crate::{ - backend::Core, - rounds::{R12, R20, R8}, -}; +/// Number of 32-bit words in the ChaCha state +const STATE_WORDS: usize = 16; -#[cfg(feature = "hchacha")] -pub use crate::xchacha::hchacha; +/// Block type used by all ChaCha variants. +type Block = GenericArray; -#[cfg(feature = "legacy")] -pub use crate::legacy::{ChaCha20Legacy, LegacyNonce}; +/// Key type used by all ChaCha variants. +pub type Key = GenericArray; -#[cfg(feature = "rng")] -pub use rng::{ - ChaCha12Rng, ChaCha12RngCore, ChaCha20Rng, ChaCha20RngCore, ChaCha8Rng, ChaCha8RngCore, -}; +/// Nonce type used by ChaCha variants. +pub type Nonce = GenericArray; -/// Size of a ChaCha20 block in bytes -pub const BLOCK_SIZE: usize = 64; +/// ChaCha8 stream cipher (reduced-round variant of [`ChaCha20`] with 8 rounds) +pub type ChaCha8 = StreamCipherCoreWrapper>; -/// Size of a ChaCha20 key in bytes -pub const KEY_SIZE: usize = 32; +/// ChaCha12 stream cipher (reduced-round variant of [`ChaCha20`] with 12 rounds) +pub type ChaCha12 = StreamCipherCoreWrapper>; -/// Number of bytes in the core (non-extended) ChaCha20 IV -const IV_SIZE: usize = 8; +/// ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce) +pub type ChaCha20 = StreamCipherCoreWrapper>; -/// State initialization constant ("expand 32-byte k") -const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; +/// The ChaCha core function. +pub struct ChaChaCore { + /// Internal state of the core function + state: [u32; STATE_WORDS], + /// Number of rounds to perform + rounds: PhantomData, +} + +impl KeySizeUser for ChaChaCore { + type KeySize = U32; +} + +impl IvSizeUser for ChaChaCore { + type IvSize = U12; +} + +impl BlockSizeUser for ChaChaCore { + type BlockSize = U64; +} + +impl KeyIvInit for ChaChaCore { + #[inline] + fn new(key: &Key, iv: &Nonce) -> Self { + let mut state = [0u32; STATE_WORDS]; + state[0..4].copy_from_slice(&CONSTANTS); + let key_chunks = key.chunks_exact(4); + for (val, chunk) in state[4..12].iter_mut().zip(key_chunks) { + *val = u32::from_le_bytes(chunk.try_into().unwrap()); + } + let iv_chunks = iv.chunks_exact(4); + for (val, chunk) in state[13..16].iter_mut().zip(iv_chunks) { + *val = u32::from_le_bytes(chunk.try_into().unwrap()); + } + Self { + state, + rounds: PhantomData, + } + } +} + +impl StreamCipherCore for ChaChaCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + let rem = u32::MAX - self.get_block_pos(); + rem.try_into().ok() + } + + fn process_with_backend(&mut self, f: impl StreamClosure) { + f.call(&mut backends::soft::Backend(self)); + } +} + +impl StreamCipherSeekCore for ChaChaCore { + type Counter = u32; + + #[inline(always)] + fn get_block_pos(&self) -> u32 { + self.state[12] + } + + #[inline(always)] + fn set_block_pos(&mut self, pos: u32) { + self.state[12] = pos; + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for ChaChaCore { + fn drop(&mut self) { + self.state.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for ChaChaCore {} diff --git a/chacha20/src/max_blocks.rs b/chacha20/src/max_blocks.rs deleted file mode 100644 index 5ace41d8..00000000 --- a/chacha20/src/max_blocks.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Maximum number of blocks that can be encrypted with ChaCha before the -//! counter overflows. - -/// Marker type for the maximum ChaCha counter value -pub trait MaxCounter: Copy { - const MAX_BLOCKS: Option; -} - -/// 32-bit counter -#[derive(Copy, Clone)] -pub struct C32; - -impl MaxCounter for C32 { - const MAX_BLOCKS: Option = Some(u32::MAX as u64); -} - -/// 64-bit counter -#[derive(Copy, Clone)] -pub struct C64; - -impl MaxCounter for C64 { - const MAX_BLOCKS: Option = None; -} diff --git a/chacha20/src/rng.rs b/chacha20/src/rng.rs deleted file mode 100644 index 672f65f6..00000000 --- a/chacha20/src/rng.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Block RNG based on rand_core::BlockRng - -use rand_core::{ - block::{BlockRng, BlockRngCore}, - CryptoRng, Error, RngCore, SeedableRng, -}; - -use crate::{ - backend::{Core, BUFFER_SIZE}, - rounds::{R12, R20, R8}, - KEY_SIZE, -}; -use core::convert::TryInto; - -/// Array wrapper used for `BlockRngCore::Results` associated types. -#[repr(transparent)] -pub struct BlockRngResults([u32; BUFFER_SIZE / 4]); - -impl Default for BlockRngResults { - fn default() -> Self { - BlockRngResults([u32::default(); BUFFER_SIZE / 4]) - } -} - -impl AsRef<[u32]> for BlockRngResults { - fn as_ref(&self) -> &[u32] { - &self.0 - } -} - -impl AsMut<[u32]> for BlockRngResults { - fn as_mut(&mut self) -> &mut [u32] { - &mut self.0 - } -} - -macro_rules! impl_chacha_rng { - ($name:ident, $core:ident, $rounds:ident, $doc:expr) => { - #[doc = $doc] - #[cfg_attr(docsrs, doc(cfg(feature = "rng")))] - pub struct $name(BlockRng<$core>); - - impl SeedableRng for $name { - type Seed = [u8; KEY_SIZE]; - - #[inline] - fn from_seed(seed: Self::Seed) -> Self { - let core = $core::from_seed(seed); - Self(BlockRng::new(core)) - } - } - - impl RngCore for $name { - #[inline] - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - #[inline] - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - - #[inline] - fn fill_bytes(&mut self, bytes: &mut [u8]) { - self.0.fill_bytes(bytes) - } - - #[inline] - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(bytes) - } - } - - impl CryptoRng for $name {} - - #[doc = "Core random number generator, for use with [`rand_core::block::BlockRng`]"] - #[cfg_attr(docsrs, doc(cfg(feature = "rng")))] - pub struct $core { - block: Core<$rounds>, - counter: u64, - } - - impl SeedableRng for $core { - type Seed = [u8; KEY_SIZE]; - - #[inline] - fn from_seed(seed: Self::Seed) -> Self { - let block = Core::new(&seed, Default::default()); - Self { block, counter: 0 } - } - } - - impl BlockRngCore for $core { - type Item = u32; - type Results = BlockRngResults; - - fn generate(&mut self, results: &mut Self::Results) { - // is this necessary? - assert!( - self.counter < u32::MAX as u64, - "maximum number of allowed ChaCha blocks exceeded" - ); - - let mut buffer = [0u8; BUFFER_SIZE]; - self.block.generate(self.counter, &mut buffer); - - for (n, chunk) in results.as_mut().iter_mut().zip(buffer.chunks_exact(4)) { - *n = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - self.counter += 1; - } - } - - impl CryptoRng for $core {} - }; -} - -impl_chacha_rng!( - ChaCha8Rng, - ChaCha8RngCore, - R8, - "Random number generator over the ChaCha8 stream cipher." -); - -impl_chacha_rng!( - ChaCha12Rng, - ChaCha12RngCore, - R12, - "Random number generator over the ChaCha12 stream cipher." -); - -impl_chacha_rng!( - ChaCha20Rng, - ChaCha20RngCore, - R20, - "Random number generator over the ChaCha20 stream cipher." -); - -#[cfg(test)] -mod tests { - use super::ChaCha20Rng; - use crate::KEY_SIZE; - use rand_core::{RngCore, SeedableRng}; - - const KEY: [u8; KEY_SIZE] = [ - 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, - ]; - - #[test] - fn test_rng_output() { - let mut rng = ChaCha20Rng::from_seed(KEY); - let mut bytes = [0u8; 13]; - - rng.fill_bytes(&mut bytes); - assert_eq!( - bytes, - [177, 105, 126, 159, 198, 70, 30, 25, 131, 209, 49, 207, 105] - ); - - rng.fill_bytes(&mut bytes); - assert_eq!( - bytes, - [167, 163, 252, 19, 79, 20, 152, 128, 232, 187, 43, 93, 35] - ); - } -} diff --git a/chacha20/src/rounds.rs b/chacha20/src/rounds.rs deleted file mode 100644 index 30b5404c..00000000 --- a/chacha20/src/rounds.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Numbers of rounds allowed to be used with the ChaCha stream cipher - -/// Marker type for a number of ChaCha rounds to perform. -pub trait Rounds: Copy { - const COUNT: usize; -} - -/// 8-rounds -#[derive(Copy, Clone)] -pub struct R8; - -impl Rounds for R8 { - const COUNT: usize = 8; -} - -/// 12-rounds -#[derive(Copy, Clone)] -pub struct R12; - -impl Rounds for R12 { - const COUNT: usize = 12; -} - -/// 20-rounds -#[derive(Copy, Clone)] -pub struct R20; - -impl Rounds for R20 { - const COUNT: usize = 20; -} diff --git a/chacha20/src/xchacha.rs b/chacha20/src/xchacha.rs index a808c0cc..4eefd080 100644 --- a/chacha20/src/xchacha.rs +++ b/chacha20/src/xchacha.rs @@ -1,27 +1,23 @@ //! XChaCha is an extended nonce variant of ChaCha -use crate::{ - backend::soft::quarter_round, - chacha::Key, - max_blocks::C64, - rounds::{Rounds, R12, R20, R8}, - ChaCha, CONSTANTS, -}; +use super::{backends::soft::quarter_round, ChaChaCore, Key, Nonce, CONSTANTS}; use cipher::{ - consts::{U16, U24, U32}, - errors::{LoopError, OverflowError}, - generic_array::GenericArray, - NewCipher, SeekNum, StreamCipher, StreamCipherSeek, + consts::{U10, U16, U24, U32, U4, U6, U64}, + generic_array::{typenum::Unsigned, GenericArray}, + BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper, + StreamCipherSeekCore, StreamClosure, }; -use core::convert::TryInto; -/// EXtended ChaCha20 nonce (192-bits/24-bytes) -pub type XNonce = cipher::Nonce; +#[cfg(feature = "zeroize")] +use cipher::zeroize::ZeroizeOnDrop; + +/// Nonce type used by XChaCha variants. +pub type XNonce = GenericArray; -/// XChaCha20 is a ChaCha20 variant with an extended 192-bit (24-byte) nonce. +/// XChaCha is a ChaCha20 variant with an extended 192-bit (24-byte) nonce. /// /// The construction is an adaptation of the same techniques used by -/// XSalsa20 as described in the paper "Extending the Salsa20 Nonce", +/// XChaCha as described in the paper "Extending the Salsa20 Nonce", /// applied to the 96-bit nonce variant of ChaCha20, and derive a /// separate subkey/nonce for each extended nonce: /// @@ -33,55 +29,66 @@ pub type XNonce = cipher::Nonce; /// and is documented in an (expired) IETF draft: /// /// -pub type XChaCha20 = XChaCha; - +pub type XChaCha20 = StreamCipherCoreWrapper>; /// XChaCha12 stream cipher (reduced-round variant of [`XChaCha20`] with 12 rounds) -pub type XChaCha12 = XChaCha; - +pub type XChaCha12 = StreamCipherCoreWrapper>; /// XChaCha8 stream cipher (reduced-round variant of [`XChaCha20`] with 8 rounds) -pub type XChaCha8 = XChaCha; +pub type XChaCha8 = StreamCipherCoreWrapper>; -/// XChaCha family stream cipher, generic around a number of rounds. -/// -/// Use the [`XChaCha8`], [`XChaCha12`], or [`XChaCha20`] type aliases to select -/// a specific number of rounds. -/// -/// Generally [`XChaCha20`] is preferred. -pub struct XChaCha(ChaCha); +/// The XChaCha core function. +pub struct XChaChaCore(ChaChaCore); -impl NewCipher for XChaCha { - /// Key size in bytes +impl KeySizeUser for XChaChaCore { type KeySize = U32; +} - /// Nonce size in bytes - type NonceSize = U24; +impl IvSizeUser for XChaChaCore { + type IvSize = U24; +} - #[allow(unused_mut, clippy::let_and_return)] - fn new(key: &Key, nonce: &XNonce) -> Self { - // TODO(tarcieri): zeroize subkey - let subkey = hchacha::(key, nonce[..16].as_ref().into()); - let mut padded_iv = GenericArray::default(); - padded_iv[4..].copy_from_slice(&nonce[16..]); - XChaCha(ChaCha::new(&subkey, &padded_iv)) +impl BlockSizeUser for XChaChaCore { + type BlockSize = U64; +} + +impl KeyIvInit for XChaChaCore { + fn new(key: &Key, iv: &XNonce) -> Self { + let subkey = hchacha::(key, iv[..16].as_ref().into()); + let mut padded_iv = Nonce::default(); + padded_iv[4..].copy_from_slice(&iv[16..]); + XChaChaCore(ChaChaCore::new(&subkey, &padded_iv)) } } -impl StreamCipher for XChaCha { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - self.0.try_apply_keystream(data) +impl StreamCipherCore for XChaChaCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + self.0.remaining_blocks() + } + + #[inline(always)] + fn process_with_backend(&mut self, f: impl StreamClosure) { + self.0.process_with_backend(f); } } -impl StreamCipherSeek for XChaCha { - fn try_current_pos(&self) -> Result { - self.0.try_current_pos() +impl StreamCipherSeekCore for XChaChaCore { + type Counter = u32; + + #[inline(always)] + fn get_block_pos(&self) -> u32 { + self.0.get_block_pos() } - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - self.0.try_seek(pos) + #[inline(always)] + fn set_block_pos(&mut self, pos: u32) { + self.0.set_block_pos(pos); } } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for XChaChaCore {} + /// The HChaCha function: adapts the ChaCha core function in the same /// manner that HSalsa adapts the Salsa function. /// @@ -96,21 +103,21 @@ impl StreamCipherSeek for XChaCha { /// For more information on HSalsa on which HChaCha is based, see: /// /// -#[cfg_attr(docsrs, doc(cfg(feature = "hchacha")))] -pub fn hchacha(key: &Key, input: &GenericArray) -> GenericArray { +pub fn hchacha(key: &Key, input: &GenericArray) -> GenericArray { let mut state = [0u32; 16]; state[..4].copy_from_slice(&CONSTANTS); - for (i, chunk) in key.chunks(4).take(8).enumerate() { - state[4 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + let key_chunks = key.chunks_exact(4); + for (v, chunk) in state[4..12].iter_mut().zip(key_chunks) { + *v = u32::from_le_bytes(chunk.try_into().unwrap()); } - - for (i, chunk) in input.chunks(4).enumerate() { - state[12 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + let input_chunks = input.chunks_exact(4); + for (v, chunk) in state[12..16].iter_mut().zip(input_chunks) { + *v = u32::from_le_bytes(chunk.try_into().unwrap()); } // R rounds consisting of R/2 column rounds and R/2 diagonal rounds - for _ in 0..(R::COUNT / 2) { + for _ in 0..R::USIZE { // column rounds quarter_round(0, 4, 8, 12, &mut state); quarter_round(1, 5, 9, 13, &mut state); @@ -126,12 +133,12 @@ pub fn hchacha(key: &Key, input: &GenericArray) -> GenericAr let mut output = GenericArray::default(); - for (i, chunk) in output.chunks_mut(4).take(4).enumerate() { - chunk.copy_from_slice(&state[i].to_le_bytes()); + for (chunk, val) in output[..16].chunks_exact_mut(4).zip(&state[..4]) { + chunk.copy_from_slice(&val.to_le_bytes()); } - for (i, chunk) in output.chunks_mut(4).skip(4).enumerate() { - chunk.copy_from_slice(&state[i + 12].to_le_bytes()); + for (chunk, val) in output[16..].chunks_exact_mut(4).zip(&state[12..]) { + chunk.copy_from_slice(&val.to_le_bytes()); } output @@ -140,32 +147,25 @@ pub fn hchacha(key: &Key, input: &GenericArray) -> GenericAr #[cfg(test)] mod hchacha20_tests { use super::*; + use hex_literal::hex; - // - // Test vectors from: - // https://tools.ietf.org/id/draft-arciszewski-xchacha-03.html#rfc.section.2.2.1 - // - - const KEY: [u8; 32] = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, - ]; + /// Test vectors from: + /// https://tools.ietf.org/id/draft-arciszewski-xchacha-03.html#rfc.section.2.2.1 + #[test] + fn test_vector() { + const KEY: [u8; 32] = hex!( + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + ); - const INPUT: [u8; 16] = [ - 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x31, 0x41, 0x59, - 0x27, - ]; + const INPUT: [u8; 16] = hex!("000000090000004a0000000031415927"); - const OUTPUT: [u8; 32] = [ - 0x82, 0x41, 0x3b, 0x42, 0x27, 0xb2, 0x7b, 0xfe, 0xd3, 0xe, 0x42, 0x50, 0x8a, 0x87, 0x7d, - 0x73, 0xa0, 0xf9, 0xe4, 0xd5, 0x8a, 0x74, 0xa8, 0x53, 0xc1, 0x2e, 0xc4, 0x13, 0x26, 0xd3, - 0xec, 0xdc, - ]; + const OUTPUT: [u8; 32] = hex!( + "82413b4227b27bfed30e42508a877d73" + "a0f9e4d58a74a853c12ec41326d3ecdc" + ); - #[test] - fn test_vector() { - let actual = hchacha::( + let actual = hchacha::( GenericArray::from_slice(&KEY), &GenericArray::from_slice(&INPUT), ); diff --git a/chacha20/tests/lib.rs b/chacha20/tests/mod.rs similarity index 51% rename from chacha20/tests/lib.rs rename to chacha20/tests/mod.rs index 8f755ccf..7d086d4a 100644 --- a/chacha20/tests/lib.rs +++ b/chacha20/tests/mod.rs @@ -1,214 +1,15 @@ //! Tests for ChaCha20 (IETF and "djb" versions) as well as XChaCha20 - -#![cfg(feature = "cipher")] - -use chacha20::ChaCha20; +use chacha20::{ChaCha20, ChaCha20Legacy, XChaCha20}; // IETF version of ChaCha20 (96-bit nonce) -cipher::stream_cipher_test!(chacha20_core, ChaCha20, "chacha20"); +cipher::stream_cipher_test!(chacha20_core, "chacha20", ChaCha20); cipher::stream_cipher_seek_test!(chacha20_seek, ChaCha20); - -mod overflow { - use cipher::{NewCipher, StreamCipher, StreamCipherSeek}; - - const OFFSET_256GB: u64 = 256u64 << 30; - const OFFSET_256PB: u64 = 256u64 << 50; - const OFFSET_1ZB: u128 = (64u128) << 64; - - #[test] - fn bad_overflow_check1() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past the last byte of 256GB"); - } - - #[test] - fn bad_overflow_check2() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 2]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt over the 256GB boundary"); - } - - #[test] - fn bad_overflow_check3() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 63]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past the last byte of 256GB"); - } - - #[test] - fn bad_overflow_check4() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 64]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past the last byte of 256GB"); - } - - #[test] - fn bad_overflow_check5() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 65]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past the last byte of 256GB"); - } - - #[test] - fn bad_overflow_check6() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB) - .expect_err("Could seek to 256GB"); - } - - #[test] - fn bad_overflow_check7() { - let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); - if let Ok(()) = cipher.try_seek(OFFSET_256GB + 63) { - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt the 64th byte past the 256GB boundary"); - } - } - - #[test] - fn xchacha_256gb() { - let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt past the last byte of 256GB"); - } - - #[test] - fn xchacha_upper_limit() { - let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_1ZB - 1) - .expect("Couldn't seek to nearly 1 zebibyte"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 1 zebibyte"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past 1 zebibyte"); - } - - #[test] - fn xchacha_has_a_big_counter() { - let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); - cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the next byte after 256PB"); - } - - #[cfg(feature = "legacy")] - #[test] - fn legacy_256gb() { - let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_256GB - 1) - .expect("Couldn't seek to nearly 256GB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 256GB"); - assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt past the last byte of 256GB"); - } - - #[cfg(feature = "legacy")] - #[test] - fn legacy_upper_limit() { - let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); - cipher - .try_seek(OFFSET_1ZB - 1) - .expect("Couldn't seek to nearly 1 zebibyte"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the last byte of 1 zebibyte"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect_err("Could encrypt past 1 zebibyte"); - } - - #[cfg(feature = "legacy")] - #[test] - fn legacy_has_a_big_counter() { - let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); - cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB"); - let mut data = [0u8; 1]; - cipher - .try_apply_keystream(&mut data) - .expect("Couldn't encrypt the next byte after 256PB"); - } -} +cipher::stream_cipher_seek_test!(xchacha20_seek, XChaCha20); +cipher::stream_cipher_seek_test!(chacha20legacy_seek, ChaCha20Legacy); mod chacha20test { - use chacha20::{ - cipher::{NewCipher, StreamCipher}, - ChaCha20, Key, Nonce, - }; + use chacha20::{ChaCha20, Key, Nonce}; + use cipher::{KeyIvInit, StreamCipher}; use hex_literal::hex; // @@ -286,7 +87,7 @@ mod chacha20test { #[rustfmt::skip] mod xchacha20 { use chacha20::{Key, XChaCha20, XNonce}; - use cipher::{NewCipher, StreamCipher}; + use cipher::{KeyIvInit, StreamCipher}; use hex_literal::hex; cipher::stream_cipher_seek_test!(xchacha20_seek, XChaCha20); diff --git a/ctr/CHANGELOG.md b/ctr/CHANGELOG.md deleted file mode 100644 index 857b3ecf..00000000 --- a/ctr/CHANGELOG.md +++ /dev/null @@ -1,59 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.8.0 (2021-07-08) -### Changed -- Make implementation generic over block size (previously it -was generic only over 128-bit block ciphers). Breaking changes -in the `CtrFlavor` API. ([#252]). - -[#252]: https://github.com/RustCrypto/stream-ciphers/pull/252 - -## 0.7.0 (2020-04-29) -### Changed -- Generic implementation of CTR ([#195]) -- Removed `Ctr32LE` mask bit ([#197]) -- Bump `cipher` crate dependency to v0.3 ([#226]) - -[#195]: https://github.com/RustCrypto/stream-ciphers/pull/195 -[#197]: https://github.com/RustCrypto/stream-ciphers/pull/197 -[#226]: https://github.com/RustCrypto/stream-ciphers/pull/226 - -## 0.6.0 (2020-10-16) -### Added -- `Ctr32BE` and `Ctr32LE` ([#170]) - -### Changed -- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177]) - -[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177 -[#170]: https://github.com/RustCrypto/stream-ciphers/pull/170 - -## 0.5.0 (2020-08-26) -### Changed -- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164]) - -[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161 -[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164 - -## 0.4.0 (2020-06-06) -### Changed -- Upgrade to the `stream-cipher` v0.4 crate ([#116], [#138]) -- Upgrade to Rust 2018 edition ([#116]) - -[#138]: https://github.com/RustCrypto/stream-ciphers/pull/138 -[#116]: https://github.com/RustCrypto/stream-ciphers/pull/121 - -## 0.3.2 (2019-03-11) - -## 0.3.0 (2018-11-01) - -## 0.2.0 (2018-10-13) - -## 0.1.1 (2018-10-13) - -## 0.1.0 (2018-07-30) diff --git a/ctr/Cargo.toml b/ctr/Cargo.toml deleted file mode 100644 index c783e5b0..00000000 --- a/ctr/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "ctr" -version = "0.8.0" # Also update html_root_url in lib.rs when bumping this -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -description = "CTR block mode of operation" -documentation = "https://docs.rs/ctr" -repository = "https://github.com/RustCrypto/stream-ciphers" -keywords = ["crypto", "stream-cipher", "trait"] -categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" - -[dependencies] -cipher = "0.3" - -[dev-dependencies] -aes = { version = "0.7", features = ["force-soft"] } # Uses `force-soft` for MSRV 1.41 -magma = "0.7" -kuznyechik = "0.7" -cipher = { version = "0.3", features = ["dev"] } -hex-literal = "0.2" diff --git a/ctr/LICENSE-APACHE b/ctr/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/ctr/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ctr/LICENSE-MIT b/ctr/LICENSE-MIT deleted file mode 100644 index f5b157a6..00000000 --- a/ctr/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 Artyom Pavlov - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ctr/README.md b/ctr/README.md deleted file mode 100644 index 8a4caff7..00000000 --- a/ctr/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# RustCrypto: Counter Mode (CTR) - -[![Crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] -[![HAZMAT][hazmat-image]][hazmat-link] - -Generic implementations of the [Counter Mode (CTR)][1] of operation for -block ciphers, which enables adapting block ciphers into stream ciphers. - -[Documentation][docs-link] - -diagram - -## ⚠️ Security Warning: [Hazmat!][hazmat-link] - -This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to -verify ciphertext integrity), which can lead to serious vulnerabilities -if used incorrectly! - -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. - -**USE AT YOUR OWN RISK!** - -## Minimum Supported Rust Version - -Rust **1.41** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/ctr.svg -[crate-link]: https://crates.io/crates/ctr -[docs-image]: https://docs.rs/ctr/badge.svg -[docs-link]: https://docs.rs/ctr/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers -[build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/ctr/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/stream-ciphers/actions?query=workflow%3Actr -[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg -[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md - -[//]: # (footnotes) - -[1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CTR diff --git a/ctr/benches/aes128.rs b/ctr/benches/aes128.rs deleted file mode 100644 index f3262a05..00000000 --- a/ctr/benches/aes128.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_sync_bench!(ctr::Ctr128); diff --git a/ctr/src/flavors.rs b/ctr/src/flavors.rs deleted file mode 100644 index 225e302a..00000000 --- a/ctr/src/flavors.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! CTR mode flavors - -use cipher::{ - generic_array::{ArrayLength, GenericArray}, - SeekNum, -}; - -mod ctr128; -mod ctr32; -mod ctr64; - -pub use ctr128::*; -pub use ctr32::*; -pub use ctr64::*; - -/// Trait implemented by different counter types used in the CTR mode. -pub trait CtrFlavor -where - Self: Default + Clone, - B: ArrayLength, -{ - /// Inner representation of nonce. - type Nonce: Clone; - /// Backend numeric type - type Backend: SeekNum; - - /// Generate block for given `nonce` value. - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray; - - /// Load nonce value from bytes. - fn load(block: &GenericArray) -> Self::Nonce; - - /// Checked addition. - fn checked_add(&self, rhs: usize) -> Option; - - /// Wrapped increment. - fn increment(&mut self); - - /// Convert from a backend value - fn from_backend(v: Self::Backend) -> Self; - - /// Convert to a backend value - fn to_backend(&self) -> Self::Backend; -} diff --git a/ctr/src/flavors/ctr128.rs b/ctr/src/flavors/ctr128.rs deleted file mode 100644 index f5a85de7..00000000 --- a/ctr/src/flavors/ctr128.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! 128-bit counter falvors. -use super::CtrFlavor; -use cipher::generic_array::{ - typenum::{operator_aliases::PartialQuot, type_operators::PartialDiv, Unsigned, U16}, - ArrayLength, GenericArray, -}; -use core::convert::TryInto; - -type ChunkSize = U16; -type Chunks = PartialQuot; -const CS: usize = ChunkSize::USIZE; - -/// 128-bit big endian counter flavor. -#[derive(Default, Clone)] -#[repr(transparent)] -pub struct Ctr128BE(u128); - -impl CtrFlavor for Ctr128BE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u128; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == Chunks::::USIZE - 1 { - self.0.wrapping_add(nonce[i]).to_be_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == Chunks::::USIZE - 1 { - u128::from_be_bytes(chunk) - } else { - u128::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} - -/// 128-bit little endian counter flavor. -#[derive(Default, Clone)] -#[repr(transparent)] -pub struct Ctr128LE(u128); - -impl CtrFlavor for Ctr128LE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u128; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == 0 { - self.0.wrapping_add(nonce[i]).to_le_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == 0 { - u128::from_le_bytes(chunk) - } else { - u128::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} diff --git a/ctr/src/flavors/ctr32.rs b/ctr/src/flavors/ctr32.rs deleted file mode 100644 index b9d10036..00000000 --- a/ctr/src/flavors/ctr32.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! 32-bit counter falvors. -use super::CtrFlavor; -use cipher::generic_array::{ - typenum::{operator_aliases::PartialQuot, type_operators::PartialDiv, Unsigned, U4}, - ArrayLength, GenericArray, -}; -use core::convert::TryInto; - -type ChunkSize = U4; -type Chunks = PartialQuot; -const CS: usize = ChunkSize::USIZE; - -/// 32-bit big endian counter flavor. -#[derive(Default, Copy, Clone)] -#[repr(transparent)] -pub struct Ctr32BE(u32); - -impl CtrFlavor for Ctr32BE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u32; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == Chunks::::USIZE - 1 { - self.0.wrapping_add(nonce[i]).to_be_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == Chunks::::USIZE - 1 { - u32::from_be_bytes(chunk) - } else { - u32::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} - -/// 32-bit little endian counter flavor. -#[derive(Default, Clone)] -#[repr(transparent)] -pub struct Ctr32LE(u32); - -impl CtrFlavor for Ctr32LE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u32; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == 0 { - self.0.wrapping_add(nonce[i]).to_le_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == 0 { - u32::from_le_bytes(chunk) - } else { - u32::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} diff --git a/ctr/src/flavors/ctr64.rs b/ctr/src/flavors/ctr64.rs deleted file mode 100644 index bad1a786..00000000 --- a/ctr/src/flavors/ctr64.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! 64-bit counter falvors. -use super::CtrFlavor; -use cipher::generic_array::{ - typenum::{operator_aliases::PartialQuot, type_operators::PartialDiv, Unsigned, U8}, - ArrayLength, GenericArray, -}; -use core::convert::TryInto; - -type ChunkSize = U8; -type Chunks = PartialQuot; -const CS: usize = ChunkSize::USIZE; - -/// 64-bit big endian counter flavor. -#[derive(Default, Copy, Clone)] -#[repr(transparent)] -pub struct Ctr64BE(u64); - -impl CtrFlavor for Ctr64BE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u64; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == Chunks::::USIZE - 1 { - self.0.wrapping_add(nonce[i]).to_be_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == Chunks::::USIZE - 1 { - u64::from_be_bytes(chunk) - } else { - u64::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} - -/// 64-bit little endian counter flavor. -#[derive(Default, Clone)] -#[repr(transparent)] -pub struct Ctr64LE(u64); - -impl CtrFlavor for Ctr64LE -where - Self: Default + Clone, - B: ArrayLength + PartialDiv, - Chunks: ArrayLength, -{ - type Nonce = GenericArray>; - type Backend = u64; - - #[inline] - fn generate_block(&self, nonce: &Self::Nonce) -> GenericArray { - let mut block = GenericArray::::default(); - for i in 0..Chunks::::USIZE { - let t = if i == 0 { - self.0.wrapping_add(nonce[i]).to_le_bytes() - } else { - nonce[i].to_ne_bytes() - }; - block[CS * i..][..CS].copy_from_slice(&t); - } - block - } - - #[inline] - fn load(block: &GenericArray) -> Self::Nonce { - let mut res = Self::Nonce::default(); - for i in 0..Chunks::::USIZE { - let chunk = block[CS * i..][..CS].try_into().unwrap(); - res[i] = if i == 0 { - u64::from_le_bytes(chunk) - } else { - u64::from_ne_bytes(chunk) - } - } - res - } - - #[inline] - fn checked_add(&self, rhs: usize) -> Option { - rhs.try_into() - .ok() - .and_then(|rhs| self.0.checked_add(rhs)) - .map(Self) - } - - #[inline] - fn increment(&mut self) { - self.0 = self.0.wrapping_add(1); - } - - #[inline] - fn to_backend(&self) -> Self::Backend { - self.0 - } - - #[inline] - fn from_backend(v: Self::Backend) -> Self { - Self(v) - } -} diff --git a/ctr/src/lib.rs b/ctr/src/lib.rs deleted file mode 100644 index c83aba98..00000000 --- a/ctr/src/lib.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! Generic implementations of CTR mode for block ciphers. -//! -//! Mode functionality is accessed using traits from re-exported -//! [`cipher`](https://docs.rs/cipher) crate. -//! -//! # ⚠️ Security Warning: [Hazmat!] -//! -//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity -//! is not verified, which can lead to serious vulnerabilities! -//! -//! # `Ctr128` Usage Example -//! -//! ``` -//! use ctr::cipher::{NewCipher, StreamCipher, StreamCipherSeek}; -//! -//! // `aes` crate provides AES block cipher implementation -//! type Aes128Ctr = ctr::Ctr128BE; -//! -//! let mut data = [1, 2, 3, 4, 5, 6, 7]; -//! -//! let key = b"very secret key."; -//! let nonce = b"and secret nonce"; -//! -//! // create cipher instance -//! let mut cipher = Aes128Ctr::new(key.into(), nonce.into()); -//! -//! // apply keystream (encrypt) -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [6, 245, 126, 124, 180, 146, 37]); -//! -//! // seek to the keystream beginning and apply it again to the `data` (decrypt) -//! cipher.seek(0); -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [1, 2, 3, 4, 5, 6, 7]); -//! ``` -//! -//! [Hazmat!]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md - -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/ctr/0.8.0" -)] -#![warn(missing_docs, rust_2018_idioms)] -#![allow(clippy::upper_case_acronyms)] - -pub use cipher; -use cipher::{ - errors::{LoopError, OverflowError}, - generic_array::typenum::Unsigned, - Block, BlockEncrypt, FromBlockCipher, ParBlocks, SeekNum, StreamCipher, StreamCipherSeek, -}; -use core::fmt; - -pub mod flavors; -use flavors::CtrFlavor; - -/// CTR mode with 128-bit big endian counter. -pub type Ctr128BE = Ctr; -/// CTR mode with 128-bit little endian counter. -pub type Ctr128LE = Ctr; -/// CTR mode with 64-bit big endian counter. -pub type Ctr64BE = Ctr; -/// CTR mode with 64-bit little endian counter. -pub type Ctr64LE = Ctr; -/// CTR mode with 32-bit big endian counter. -pub type Ctr32BE = Ctr; -/// CTR mode with 32-bit little endian counter. -pub type Ctr32LE = Ctr; - -/// Generic CTR block mode isntance. -#[derive(Clone)] -pub struct Ctr -where - B: BlockEncrypt, - F: CtrFlavor, -{ - cipher: B, - nonce: >::Nonce, - counter: F, - buffer: Block, - buf_pos: u8, -} - -impl Ctr -where - B: BlockEncrypt, - F: CtrFlavor, -{ - fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> { - let bs = B::BlockSize::USIZE; - let leftover_bytes = bs - self.buf_pos as usize; - if data.len() < leftover_bytes { - return Ok(()); - } - let blocks = 1 + (data.len() - leftover_bytes) / bs; - self.counter - .checked_add(blocks) - .ok_or(LoopError) - .map(|_| ()) - } - - /// Seek to the given block - // TODO: replace with a trait-based method - pub fn seek_block(&mut self, block: F::Backend) { - self.counter = F::from_backend(block); - } - - /// Return number of the current block - // TODO: replace with a trait-based method - pub fn current_block(&self) -> F::Backend { - self.counter.to_backend() - } -} - -impl FromBlockCipher for Ctr -where - B: BlockEncrypt, - F: CtrFlavor, -{ - type BlockCipher = B; - type NonceSize = B::BlockSize; - - #[inline] - fn from_block_cipher(cipher: B, nonce: &Block) -> Self { - let nonce = F::load(nonce); - Self { - cipher, - buffer: Default::default(), - nonce, - counter: Default::default(), - buf_pos: 0, - } - } -} - -impl StreamCipher for Ctr -where - B: BlockEncrypt, - F: CtrFlavor, -{ - fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { - self.check_data_len(data)?; - let bs = B::BlockSize::USIZE; - let pos = self.buf_pos as usize; - debug_assert!(bs > pos); - - let mut counter = self.counter.clone(); - if pos != 0 { - if data.len() < bs - pos { - let n = pos + data.len(); - xor(data, &self.buffer[pos..n]); - self.buf_pos = n as u8; - return Ok(()); - } else { - let (l, r) = data.split_at_mut(bs - pos); - data = r; - xor(l, &self.buffer[pos..]); - counter.increment(); - } - } - - // Process blocks in parallel if cipher supports it - let pb = B::ParBlocks::USIZE; - if pb != 1 { - let mut chunks = data.chunks_exact_mut(bs * pb); - let mut blocks: ParBlocks = Default::default(); - for chunk in &mut chunks { - for b in blocks.iter_mut() { - *b = counter.generate_block(&self.nonce); - counter.increment(); - } - - self.cipher.encrypt_par_blocks(&mut blocks); - xor(chunk, to_slice::(&blocks)); - } - data = chunks.into_remainder(); - } - - let mut chunks = data.chunks_exact_mut(bs); - for chunk in &mut chunks { - let mut block = counter.generate_block(&self.nonce); - counter.increment(); - self.cipher.encrypt_block(&mut block); - xor(chunk, &block); - } - - let rem = chunks.into_remainder(); - if !rem.is_empty() { - self.buffer = counter.generate_block(&self.nonce); - self.cipher.encrypt_block(&mut self.buffer); - xor(rem, &self.buffer[..rem.len()]); - } - self.buf_pos = rem.len() as u8; - self.counter = counter; - Ok(()) - } -} - -impl StreamCipherSeek for Ctr -where - B: BlockEncrypt, - F: CtrFlavor, -{ - fn try_current_pos(&self) -> Result { - T::from_block_byte(self.counter.to_backend(), self.buf_pos, B::BlockSize::U8) - } - - fn try_seek(&mut self, pos: S) -> Result<(), LoopError> { - let res: (F::Backend, u8) = pos.to_block_byte(B::BlockSize::U8)?; - self.counter = F::from_backend(res.0); - self.buf_pos = res.1; - if self.buf_pos != 0 { - let mut block = self.counter.generate_block(&self.nonce); - self.cipher.encrypt_block(&mut block); - self.buffer = block; - } - Ok(()) - } -} - -impl fmt::Debug for Ctr -where - B: BlockEncrypt + fmt::Debug, - F: CtrFlavor + fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "Ctr-{:?}-{:?}", self.counter, self.cipher) - } -} - -#[inline(always)] -fn xor(buf: &mut [u8], key: &[u8]) { - debug_assert_eq!(buf.len(), key.len()); - for (a, b) in buf.iter_mut().zip(key) { - *a ^= *b; - } -} - -#[inline(always)] -fn to_slice(blocks: &ParBlocks) -> &[u8] { - let blocks_len = C::BlockSize::to_usize() * C::ParBlocks::to_usize(); - // SAFETY: - // - `blocks` is a `GenericArray, C::ParBlocks>`, and - // so `blocks.as_ptr()` returns a pointer to the `&[GenericArray]` - // slice represented by `blocks`. - // - `GenericArray` has the same layout as `[T; N]`. - // - `[[u8; M]; N]` has the same layout as `[u8; M * N]`. - unsafe { core::slice::from_raw_parts(blocks.as_ptr() as *const u8, blocks_len) } -} diff --git a/ctr/tests/ctr128/data/aes128-ctr.blb b/ctr/tests/ctr128/data/aes128-ctr.blb deleted file mode 100644 index d721e4ec..00000000 Binary files a/ctr/tests/ctr128/data/aes128-ctr.blb and /dev/null differ diff --git a/ctr/tests/ctr128/data/aes256-ctr.blb b/ctr/tests/ctr128/data/aes256-ctr.blb deleted file mode 100644 index 47daaf84..00000000 Binary files a/ctr/tests/ctr128/data/aes256-ctr.blb and /dev/null differ diff --git a/ctr/tests/ctr128/mod.rs b/ctr/tests/ctr128/mod.rs deleted file mode 100644 index af267656..00000000 --- a/ctr/tests/ctr128/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -type Aes128Ctr = ctr::Ctr128BE; -type Aes256Ctr = ctr::Ctr128BE; - -cipher::stream_cipher_test!(aes128_ctr_core, Aes128Ctr, "aes128-ctr"); -cipher::stream_cipher_test!(aes256_ctr_core, Aes256Ctr, "aes256-ctr"); -cipher::stream_cipher_seek_test!(aes128_ctr_seek, Aes128Ctr); -cipher::stream_cipher_seek_test!(aes256_ctr_seek, Aes256Ctr); diff --git a/ctr/tests/ctr32/big_endian.rs b/ctr/tests/ctr32/big_endian.rs deleted file mode 100644 index 5206fa72..00000000 --- a/ctr/tests/ctr32/big_endian.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Counter Mode with a 32-bit big endian counter - -use cipher::{NewCipher, StreamCipher}; -use hex_literal::hex; - -type Aes128Ctr = ctr::Ctr32BE; - -const KEY: &[u8; 16] = &hex!("000102030405060708090A0B0C0D0E0F"); -const NONCE1: &[u8; 16] = &hex!("11111111111111111111111111111111"); -const NONCE2: &[u8; 16] = &hex!("222222222222222222222222FFFFFFFE"); - -#[test] -fn counter_incr() { - let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); - assert_eq!(ctr.current_block(), 0); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - assert_eq!(ctr.current_block(), 4); - assert_eq!( - &buffer[..], - &hex!( - "35D14E6D3E3A279CF01E343E34E7DED36EEADB04F42E2251AB4377F257856DBA - 0AB37657B9C2AA09762E518FC9395D5304E96C34CCD2F0A95CDE7321852D90C0" - )[..] - ); -} - -#[test] -fn counter_seek() { - let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); - ctr.seek_block(1); - assert_eq!(ctr.current_block(), 1); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - assert_eq!(ctr.current_block(), 5); - assert_eq!( - &buffer[..], - &hex!( - "6EEADB04F42E2251AB4377F257856DBA0AB37657B9C2AA09762E518FC9395D53 - 04E96C34CCD2F0A95CDE7321852D90C0F7441EAB3811A03FDBD162AEC402F5AA" - )[..] - ); -} - -#[test] -fn keystream_xor() { - let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); - let mut buffer = [1u8; 64]; - - ctr.apply_keystream(&mut buffer); - assert_eq!( - &buffer[..], - &hex!( - "34D04F6C3F3B269DF11F353F35E6DFD26FEBDA05F52F2350AA4276F356846CBB - 0BB27756B8C3AB08772F508EC8385C5205E86D35CDD3F1A85DDF7220842C91C1" - )[..] - ); -} - -#[test] -fn counter_wrap() { - let mut ctr = Aes128Ctr::new(KEY.into(), NONCE2.into()); - assert_eq!(ctr.current_block(), 0); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - assert_eq!(ctr.current_block(), 4); - assert_eq!( - &buffer[..], - &hex!( - "58FC849D1CF53C54C63E1B1D15CB3C8AAA335F72135585E9FF943F4DAC77CB63 - BD1AE8716BE69C3B4D886B222B9B4E1E67548EF896A96E2746D8CA6476D8B183" - )[..] - ); -} diff --git a/ctr/tests/ctr32/little_endian.rs b/ctr/tests/ctr32/little_endian.rs deleted file mode 100644 index c6a7a2c1..00000000 --- a/ctr/tests/ctr32/little_endian.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Counter Mode with a 32-bit little endian counter - -use cipher::{consts::U16, generic_array::GenericArray, NewCipher, StreamCipher}; -use hex_literal::hex; - -type Aes128Ctr = ctr::Ctr32LE; - -const KEY: &[u8; 16] = &hex!("000102030405060708090A0B0C0D0E0F"); -const NONCE1: &[u8; 16] = &hex!("11111111111111111111111111111111"); -const NONCE2: &[u8; 16] = &hex!("FEFFFFFF222222222222222222222222"); - -/// Compute nonce as used by AES-GCM-SIV -fn nonce(bytes: &[u8; 16]) -> GenericArray { - let mut n = *bytes; - n[15] |= 0x80; - n.into() -} - -#[test] -fn counter_incr() { - let mut ctr = Aes128Ctr::new(KEY.into(), &nonce(NONCE1)); - assert_eq!(ctr.current_block(), 0); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - // assert_eq!(ctr.current_ctr(), 4); - assert_eq!( - &buffer[..], - &hex!( - "2A0680B210CAD45E886D7EF6DAB357C9F18B39AFF6930FDB2D9FCE34261FF699 - EB96774669D24B560C9AD028C5C39C4580775A82065256B4787DC91C6942B700" - )[..] - ); -} - -#[test] -fn counter_seek() { - let mut ctr = Aes128Ctr::new(KEY.into(), &nonce(NONCE1)); - ctr.seek_block(1); - assert_eq!(ctr.current_block(), 1); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - assert_eq!(ctr.current_block(), 5); - assert_eq!( - &buffer[..], - &hex!( - "F18B39AFF6930FDB2D9FCE34261FF699EB96774669D24B560C9AD028C5C39C45 - 80775A82065256B4787DC91C6942B7001564DDA1B07DCED9201AB71BAF06905B" - )[..] - ); -} - -#[test] -fn keystream_xor() { - let mut ctr = Aes128Ctr::new(KEY.into(), &nonce(NONCE1)); - let mut buffer = [1u8; 64]; - - ctr.apply_keystream(&mut buffer); - assert_eq!( - &buffer[..], - &hex!( - "2B0781B311CBD55F896C7FF7DBB256C8F08A38AEF7920EDA2C9ECF35271EF798 - EA97764768D34A570D9BD129C4C29D4481765B83075357B5797CC81D6843B601" - )[..] - ); -} - -#[test] -fn counter_wrap() { - let mut ctr = Aes128Ctr::new(KEY.into(), &nonce(NONCE2)); - assert_eq!(ctr.current_block(), 0); - - let mut buffer = [0u8; 64]; - ctr.apply_keystream(&mut buffer); - - assert_eq!(ctr.current_block(), 4); - assert_eq!( - &buffer[..], - &hex!( - "A1E649D8B382293DC28375C42443BB6A226BAADC9E9CCA8214F56E07A4024E06 - 6355A0DA2E08FB00112FFA38C26189EE55DD5B0B130ED87096FE01B59A665A60" - )[..] - ); -} diff --git a/ctr/tests/ctr32/mod.rs b/ctr/tests/ctr32/mod.rs deleted file mode 100644 index c9577016..00000000 --- a/ctr/tests/ctr32/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Counter Mode with a 32-bit counter. -//! -//! NOTE: AES-128-CTR test vectors used by these tests were generated by first -//! integration testing the implementation in the contexts of AES-GCM and -//! AES-GCM-SIV, with the former tested against the NIST CAVS vectors, and the -//! latter against the RFC8452 test vectors. - -mod big_endian; -mod little_endian; diff --git a/ctr/tests/gost/mod.rs b/ctr/tests/gost/mod.rs deleted file mode 100644 index ac35da33..00000000 --- a/ctr/tests/gost/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -use cipher::{NewCipher, StreamCipher}; -use hex_literal::hex; - -type MagmaCtr = ctr::Ctr32BE; -type KuznyechikCtr = ctr::Ctr64BE; - -/// Test vectors from GOST R 34.13-2015 (Section A.1.2) -#[test] -#[rustfmt::skip] -fn kuznyechik() { - let key = hex!(" - 8899aabbccddeeff0011223344556677 - fedcba98765432100123456789abcdef - "); - let nonce = hex!("1234567890abcef00000000000000000"); - let mut pt = hex!(" - 1122334455667700ffeeddccbbaa9988 - 00112233445566778899aabbcceeff0a - 112233445566778899aabbcceeff0a00 - 2233445566778899aabbcceeff0a0011 - "); - let ct = hex!(" - f195d8bec10ed1dbd57b5fa240bda1b8 - 85eee733f6a13e5df33ce4b33c45dee4 - a5eae88be6356ed3d5e877f13564a3a5 - cb91fab1f20cbab6d1c6d15820bdba73 - "); - let mut cipher = KuznyechikCtr::new(&key.into(), &nonce.into()); - cipher.apply_keystream(&mut pt); - assert_eq!(pt[..], ct[..]); -} - -/// Test vectors from GOST R 34.13-2015 (Section A.2.2) -#[test] -#[rustfmt::skip] -fn magma() { - let key = hex!(" - ffeeddccbbaa99887766554433221100 - f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff - "); - let nonce = hex!("1234567800000000"); - let mut pt = hex!(" - 92def06b3c130a59 - db54c704f8189d20 - 4a98fb2e67a8024c - 8912409b17b57e41 - "); - let ct = hex!(" - 4e98110c97b7b93c - 3e250d93d6e85d69 - 136d868807b2dbef - 568eb680ab52a12d - "); - let mut cipher = MagmaCtr::new(&key.into(), &nonce.into()); - cipher.apply_keystream(&mut pt); - assert_eq!(pt[..], ct[..]); -} diff --git a/ctr/tests/lib.rs b/ctr/tests/lib.rs deleted file mode 100644 index 0f91835c..00000000 --- a/ctr/tests/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Counter Mode Tests - -mod ctr128; -mod ctr32; -mod gost; diff --git a/hc-256/CHANGELOG.md b/hc-256/CHANGELOG.md index fe267de9..d2204984 100644 --- a/hc-256/CHANGELOG.md +++ b/hc-256/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.5.0 (2022-02-10) +### Changed +- Bump `cipher` dependency to v0.4 ([#276]) + +[#276]: https://github.com/RustCrypto/stream-ciphers/pull/276 + ## 0.4.1 (2021-07-20) ### Changed - Pin `zeroize` dependency to v1.3 ([#256]) diff --git a/hc-256/Cargo.toml b/hc-256/Cargo.toml index 0fd248ca..8eb7475e 100644 --- a/hc-256/Cargo.toml +++ b/hc-256/Cargo.toml @@ -1,15 +1,28 @@ [package] name = "hc-256" -version = "0.4.1" # Also update html_root_url in lib.rs when bumping this -authors = ["Eric McCorkle "] -license = "MIT OR Apache-2.0" +version = "0.5.0" # Also update html_root_url in lib.rs when bumping this description = "HC-256 Stream Cipher" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/hc-256" repository = "https://github.com/RustCrypto/stream-ciphers" keywords = ["crypto", "stream-cipher", "trait"] categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" [dependencies] -cipher = "0.3" -zeroize = { version = "=1.3", optional = true, default-features = false } +cipher = "0.4" + +[dev-dependencies] +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3" + +[features] +std = ["cipher/std"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/hc-256/README.md b/hc-256/README.md index abc02674..14d3d91a 100644 --- a/hc-256/README.md +++ b/hc-256/README.md @@ -26,7 +26,7 @@ USE AT YOUR OWN RISK! ## Minimum Supported Rust Version -Rust **1.41** or higher. +Rust **1.56** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -58,7 +58,7 @@ dual licensed as above, without any additional terms or conditions. [docs-image]: https://docs.rs/hc-256/badge.svg [docs-link]: https://docs.rs/hc-256/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg [hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md [build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/hc-256/badge.svg?branch=master&event=push diff --git a/hc-256/benches/mod.rs b/hc-256/benches/mod.rs new file mode 100644 index 00000000..bf712a50 --- /dev/null +++ b/hc-256/benches/mod.rs @@ -0,0 +1,10 @@ +#![feature(test)] +extern crate test; + +cipher::stream_cipher_bench!( + hc_256::Hc256; + hc256_bench1_16b 16; + hc256_bench2_256b 256; + hc256_bench3_1kib 1024; + hc256_bench4_16kib 16384; +); diff --git a/hc-256/src/lib.rs b/hc-256/src/lib.rs index 27310f74..000ea013 100644 --- a/hc-256/src/lib.rs +++ b/hc-256/src/lib.rs @@ -1,10 +1,59 @@ -//! HC-256 Stream Cipher +//! Implementation of the [HC-256] stream cipher. +//! +//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate. +//! +//! # ⚠️ Security Warning: Hazmat! +//! +//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity +//! is not verified, which can lead to serious vulnerabilities! +//! +//! USE AT YOUR OWN RISK! +//! +//! # Example +//! ``` +//! use hc_256::Hc256; +//! // Import relevant traits +//! use hc_256::cipher::{KeyIvInit, StreamCipher}; +//! use hex_literal::hex; +//! +//! let key = [0x42; 32]; +//! let nonce = [0x24; 32]; +//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F"); +//! let ciphertext = hex!("ca982177 325cd40e bc208045 066c420f"); +//! +//! // Key and IV must be references to the `GenericArray` type. +//! // Here we use the `Into` trait to convert arrays into it. +//! let mut cipher = Hc256::new(&key.into(), &nonce.into()); +//! +//! let mut buffer = plaintext.clone(); +//! +//! // apply keystream (encrypt) +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, ciphertext); +//! +//! let ciphertext = buffer.clone(); +//! +//! // decrypt ciphertext by applying keystream again +//! let mut cipher = Hc256::new(&key.into(), &nonce.into()); +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, plaintext); +//! +//! // stream ciphers can be used with streaming messages +//! let mut cipher = Hc256::new(&key.into(), &nonce.into()); +//! for chunk in buffer.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buffer, ciphertext); +//! ``` +//! +//! [HC-256]: https://en.wikipedia.org/wiki/HC-256 #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/hc-256/0.4.1" + html_root_url = "https://docs.rs/hc-256/0.5.0" )] #![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] @@ -12,13 +61,14 @@ pub use cipher; use cipher::{ - consts::U32, errors::LoopError, generic_array::GenericArray, NewCipher, StreamCipher, + consts::{U1, U32, U4}, + AlgorithmName, Block, BlockSizeUser, Iv, IvSizeUser, Key, KeyIvInit, KeySizeUser, + ParBlocksSizeUser, StreamBackend, StreamCipherCore, StreamCipherCoreWrapper, StreamClosure, }; +use core::fmt; -#[cfg(cargo_feature = "zeroize")] -use std::ops::Drop; -#[cfg(cargo_feature = "zeroize")] -use zeroize::Zeroize; +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; const TABLE_SIZE: usize = 1024; const TABLE_MASK: usize = TABLE_SIZE - 1; @@ -28,47 +78,43 @@ const KEY_WORDS: usize = KEY_BITS / 32; const IV_BITS: usize = 256; const IV_WORDS: usize = IV_BITS / 32; -/// The HC-256 stream cipher -pub struct Hc256 { +/// The HC-256 stream cipher core +pub type Hc256 = StreamCipherCoreWrapper; + +/// The HC-256 stream cipher core +pub struct Hc256Core { ptable: [u32; TABLE_SIZE], qtable: [u32; TABLE_SIZE], - word: u32, idx: u32, - offset: u8, } -impl NewCipher for Hc256 { - /// Key size in bytes - type KeySize = U32; - /// Nonce size in bytes - type NonceSize = U32; +impl BlockSizeUser for Hc256Core { + type BlockSize = U4; +} - fn new(key: &GenericArray, iv: &GenericArray) -> Self { - let mut out = Hc256::create(); - out.init(key.as_slice(), iv.as_slice()); - out - } +impl KeySizeUser for Hc256Core { + type KeySize = U32; } -impl StreamCipher for Hc256 { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - self.process(data); - Ok(()) - } +impl IvSizeUser for Hc256Core { + type IvSize = U32; } -impl Hc256 { - fn create() -> Hc256 { - Hc256 { +impl KeyIvInit for Hc256Core { + fn new(key: &Key, iv: &Iv) -> Self { + fn f1(x: u32) -> u32 { + x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) + } + + fn f2(x: u32) -> u32 { + x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) + } + + let mut out = Self { ptable: [0; TABLE_SIZE], qtable: [0; TABLE_SIZE], - word: 0, idx: 0, - offset: 0, - } - } - - fn init(&mut self, key: &[u8], iv: &[u8]) { + }; let mut data = [0; INIT_SIZE]; for i in 0..KEY_WORDS { @@ -93,25 +139,43 @@ impl Hc256 { .wrapping_add(i as u32); } - self.ptable[..TABLE_SIZE].clone_from_slice(&data[512..(TABLE_SIZE + 512)]); - self.qtable[..TABLE_SIZE].clone_from_slice(&data[1536..(TABLE_SIZE + 1536)]); - - self.idx = 0; + out.ptable[..TABLE_SIZE].clone_from_slice(&data[512..(TABLE_SIZE + 512)]); + out.qtable[..TABLE_SIZE].clone_from_slice(&data[1536..(TABLE_SIZE + 1536)]); - #[cfg(cargo_feature = "zeroize")] - data.zeroize(); + out.idx = 0; for _ in 0..4096 { - self.gen_word(); + out.gen_word(); } - // This forces generation of the first block - #[cfg(cargo_feature = "zeroize")] - self.word.zeroize(); + out + } +} + +impl StreamCipherCore for Hc256Core { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + None + } + + fn process_with_backend(&mut self, f: impl StreamClosure) { + f.call(&mut Backend(self)); + } +} - self.offset = 4; +impl AlgorithmName for Hc256Core { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Hc256") } +} +impl fmt::Debug for Hc256Core { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Hc256Core { ... }") + } +} + +impl Hc256Core { #[inline] fn g1(&self, x: u32, y: u32) -> u32 { (x.rotate_right(10) ^ y.rotate_right(23)) @@ -144,7 +208,6 @@ impl Hc256 { let i = self.idx as usize; let j = self.idx as usize & TABLE_MASK; - self.offset = 0; self.idx = (self.idx + 1) & (2048 - 1); if i < 1024 { @@ -167,72 +230,35 @@ impl Hc256 { self.h2(self.qtable[j.wrapping_sub(12) & TABLE_MASK]) ^ self.qtable[j] } } - - fn process(&mut self, data: &mut [u8]) { - let mut i = 0; - let mut word: u32 = self.word; - - // First, use the remaining part of the current word. - for j in self.offset..4 { - data[i] ^= ((word >> (j * 8)) & 0xff) as u8; - i += 1; - } - - let mainlen = (data.len() - i) / 4; - let leftover = (data.len() - i) % 4; - - // Process all the whole words - for _ in 0..mainlen { - word = self.gen_word(); - - for j in 0..4 { - data[i] ^= ((word >> (j * 8)) & 0xff) as u8; - i += 1; - } - } - - // Process the end of the block - if leftover != 0 { - word = self.gen_word(); - - for j in 0..leftover { - data[i] ^= ((word >> (j * 8)) & 0xff) as u8; - i += 1; - } - - self.offset = leftover as u8; - } else { - self.offset = 4; - } - - self.word = word; - } } -#[cfg(cargo_feature = "zeroize")] -impl Zeroize for Hc256 { - fn zeroize(&mut self) { +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Hc256Core { + fn drop(&mut self) { self.ptable.zeroize(); self.qtable.zeroize(); - self.word.zeroize(); self.idx.zeroize(); - self.offset.zeroize(); } } -#[cfg(cargo_feature = "zeroize")] -impl Drop for Hc256 { - fn drop(&mut self) { - self.zeroize(); - } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Hc256Core {} + +struct Backend<'a>(&'a mut Hc256Core); + +impl<'a> BlockSizeUser for Backend<'a> { + type BlockSize = ::BlockSize; } -#[inline] -fn f1(x: u32) -> u32 { - x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) +impl<'a> ParBlocksSizeUser for Backend<'a> { + type ParBlocksSize = U1; } -#[inline] -fn f2(x: u32) -> u32 { - x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) +impl<'a> StreamBackend for Backend<'a> { + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + block.copy_from_slice(&self.0.gen_word().to_le_bytes()); + } } diff --git a/hc-256/tests/lib.rs b/hc-256/tests/lib.rs deleted file mode 100644 index b8e7a3e1..00000000 --- a/hc-256/tests/lib.rs +++ /dev/null @@ -1,262 +0,0 @@ -use cipher::{generic_array::GenericArray, NewCipher, StreamCipher}; -use hc_256::Hc256; - -#[cfg(test)] -const KEY_BYTES: usize = 256 / 8; - -#[cfg(test)] -const IV_BYTES: usize = 256 / 8; - -#[cfg(test)] -const PAPER_KEY0: [u8; KEY_BYTES] = [0; KEY_BYTES]; - -#[cfg(test)] -const PAPER_KEY1: [u8; KEY_BYTES] = [ - 0x55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, -]; - -#[cfg(test)] -const PAPER_IV0: [u8; IV_BYTES] = [0; KEY_BYTES]; - -#[cfg(test)] -const PAPER_IV1: [u8; IV_BYTES] = [ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -#[cfg(test)] -const EXPECTED_PAPER_KEY0_IV0: [u8; 64] = [ - 0x5b, 0x07, 0x89, 0x85, 0xd8, 0xf6, 0xf3, 0x0d, 0x42, 0xc5, 0xc0, 0x2f, 0xa6, 0xb6, 0x79, 0x51, - 0x53, 0xf0, 0x65, 0x34, 0x80, 0x1f, 0x89, 0xf2, 0x4e, 0x74, 0x24, 0x8b, 0x72, 0x0b, 0x48, 0x18, - 0xcd, 0x92, 0x27, 0xec, 0xeb, 0xcf, 0x4d, 0xbf, 0x8d, 0xbf, 0x69, 0x77, 0xe4, 0xae, 0x14, 0xfa, - 0xe8, 0x50, 0x4c, 0x7b, 0xc8, 0xa9, 0xf3, 0xea, 0x6c, 0x01, 0x06, 0xf5, 0x32, 0x7e, 0x69, 0x81, -]; - -#[cfg(test)] -const EXPECTED_PAPER_KEY0_IV1: [u8; 64] = [ - 0xaf, 0xe2, 0xa2, 0xbf, 0x4f, 0x17, 0xce, 0xe9, 0xfe, 0xc2, 0x05, 0x8b, 0xd1, 0xb1, 0x8b, 0xb1, - 0x5f, 0xc0, 0x42, 0xee, 0x71, 0x2b, 0x31, 0x01, 0xdd, 0x50, 0x1f, 0xc6, 0x0b, 0x08, 0x2a, 0x50, - 0x06, 0xc7, 0xfe, 0xed, 0x41, 0x92, 0x3d, 0x63, 0x48, 0xc4, 0xda, 0xa6, 0xff, 0x61, 0x85, 0xaf, - 0x5a, 0x13, 0x04, 0x5e, 0x34, 0xc4, 0x48, 0x94, 0xf3, 0xe9, 0xe7, 0x2d, 0xdf, 0x0b, 0x52, 0x37, -]; - -#[cfg(test)] -const EXPECTED_PAPER_KEY1_IV0: [u8; 64] = [ - 0x1c, 0x40, 0x4a, 0xfe, 0x4f, 0xe2, 0x5f, 0xed, 0x95, 0x8f, 0x9a, 0xd1, 0xae, 0x36, 0xc0, 0x6f, - 0x88, 0xa6, 0x5a, 0x3c, 0xc0, 0xab, 0xe2, 0x23, 0xae, 0xb3, 0x90, 0x2f, 0x42, 0x0e, 0xd3, 0xa8, - 0x6c, 0x3a, 0xf0, 0x59, 0x44, 0xeb, 0x39, 0x6e, 0xfb, 0x79, 0x75, 0x8f, 0x5e, 0x7a, 0x13, 0x70, - 0xd8, 0xb7, 0x10, 0x6d, 0xcd, 0xf7, 0xd0, 0xad, 0xda, 0x23, 0x34, 0x72, 0xe6, 0xdd, 0x75, 0xf5, -]; - -#[test] -fn test_key0_iv0() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf = [0; 64]; - - cipher.apply_keystream(&mut buf); - - for i in 0..64 { - assert_eq!(buf[i], EXPECTED_PAPER_KEY0_IV0[i]) - } -} - -#[test] -fn test_key0_iv0_offset_1() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 1]; - let mut buf2 = [0; 63]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..1 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..63 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 1]) - } -} - -#[test] -fn test_key0_iv0_offset_2() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 2]; - let mut buf2 = [0; 62]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..2 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..62 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 2]) - } -} - -#[test] -fn test_key0_iv0_offset_3() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 3]; - let mut buf2 = [0; 61]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..3 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..61 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 3]) - } -} - -#[test] -fn test_key0_iv0_offset_4() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 4]; - let mut buf2 = [0; 60]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..4 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..60 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 4]) - } -} - -#[test] -fn test_key0_iv0_offset_5() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 5]; - let mut buf2 = [0; 59]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..5 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..59 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 5]) - } -} - -#[test] -fn test_key0_iv0_offset_6() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 6]; - let mut buf2 = [0; 58]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..6 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..58 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 6]) - } -} - -#[test] -fn test_key0_iv0_offset_7() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 7]; - let mut buf2 = [0; 57]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..7 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..57 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 7]) - } -} - -#[test] -fn test_key0_iv0_offset_8() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV0), - ); - let mut buf1 = [0; 8]; - let mut buf2 = [0; 56]; - - cipher.apply_keystream(&mut buf1); - cipher.apply_keystream(&mut buf2); - - for i in 0..8 { - assert_eq!(buf1[i], EXPECTED_PAPER_KEY0_IV0[i]) - } - - for i in 0..56 { - assert_eq!(buf2[i], EXPECTED_PAPER_KEY0_IV0[i + 8]) - } -} - -#[test] -fn test_key1_iv0() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY1), - &GenericArray::from(PAPER_IV0), - ); - let mut buf = [0; 64]; - - cipher.apply_keystream(&mut buf); - - for i in 0..64 { - assert_eq!(buf[i], EXPECTED_PAPER_KEY1_IV0[i]) - } -} - -#[test] -fn test_key0_iv1() { - let mut cipher = Hc256::new( - &GenericArray::from(PAPER_KEY0), - &GenericArray::from(PAPER_IV1), - ); - let mut buf = [0; 64]; - - cipher.apply_keystream(&mut buf); - - for i in 0..64 { - assert_eq!(buf[i], EXPECTED_PAPER_KEY0_IV1[i]) - } -} diff --git a/hc-256/tests/mod.rs b/hc-256/tests/mod.rs new file mode 100644 index 00000000..8e6839fa --- /dev/null +++ b/hc-256/tests/mod.rs @@ -0,0 +1,78 @@ +use cipher::{KeyIvInit, StreamCipher}; +use hc_256::Hc256; +use hex_literal::hex; + +const KEY_BYTES: usize = 256 / 8; + +const IV_BYTES: usize = 256 / 8; + +const KEY0: [u8; KEY_BYTES] = [0; KEY_BYTES]; + +const KEY1: [u8; KEY_BYTES] = hex!( + "55000000000000000000000000000000" + "00000000000000000000000000000000" +); + +const IV0: [u8; IV_BYTES] = [0; KEY_BYTES]; + +const IV1: [u8; IV_BYTES] = hex!( + "01000000000000000000000000000000" + "00000000000000000000000000000000" +); + +const EXPECTED_KEY0_IV0: [u8; 64] = hex!( + "5b078985d8f6f30d42c5c02fa6b67951" + "53f06534801f89f24e74248b720b4818" + "cd9227ecebcf4dbf8dbf6977e4ae14fa" + "e8504c7bc8a9f3ea6c0106f5327e6981" +); + +const EXPECTED_KEY0_IV1: [u8; 64] = hex!( + "afe2a2bf4f17cee9fec2058bd1b18bb1" + "5fc042ee712b3101dd501fc60b082a50" + "06c7feed41923d6348c4daa6ff6185af" + "5a13045e34c44894f3e9e72ddf0b5237" +); + +const EXPECTED_KEY1_IV0: [u8; 64] = hex!( + "1c404afe4fe25fed958f9ad1ae36c06f" + "88a65a3cc0abe223aeb3902f420ed3a8" + "6c3af05944eb396efb79758f5e7a1370" + "d8b7106dcdf7d0adda233472e6dd75f5" +); + +#[test] +fn test_hc256_key0_iv0() { + for n in 1..64 { + let mut cipher = Hc256::new_from_slices(&KEY0, &IV0).unwrap(); + let mut buf = EXPECTED_KEY0_IV0; + for chunk in buf.chunks_mut(n) { + cipher.apply_keystream(chunk); + } + assert!(buf.iter().all(|&v| v == 0)); + } +} + +#[test] +fn test_hc256_key0_iv1() { + for n in 1..64 { + let mut cipher = Hc256::new_from_slices(&KEY0, &IV1).unwrap(); + let mut buf = EXPECTED_KEY0_IV1; + for chunk in buf.chunks_mut(n) { + cipher.apply_keystream(chunk); + } + assert!(buf.iter().all(|&v| v == 0)); + } +} + +#[test] +fn test_hc256_key1_iv0() { + for n in 1..64 { + let mut cipher = Hc256::new_from_slices(&KEY1, &IV0).unwrap(); + let mut buf = EXPECTED_KEY1_IV0; + for chunk in buf.chunks_mut(n) { + cipher.apply_keystream(chunk); + } + assert!(buf.iter().all(|&v| v == 0)); + } +} diff --git a/ofb/CHANGELOG.md b/ofb/CHANGELOG.md deleted file mode 100644 index 216357f9..00000000 --- a/ofb/CHANGELOG.md +++ /dev/null @@ -1,44 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.5.1 (2021-04-30) -### Changed -- Removed redundant `NewBlockCipher` bound from `FromBlockCipher` implementation ([#236]) - -[#236]: https://github.com/RustCrypto/stream-ciphers/pull/236 - -## 0.5.0 (2021-04-29) -### Changed -- Bump `cipher` crate dependency to v0.3 release ([#226]) -- Bump `aes` dev dependency to v0.7 release ([#232]) - -[#226]: https://github.com/RustCrypto/stream-ciphers/pull/226 -[#232]: https://github.com/RustCrypto/stream-ciphers/pull/232 - -## 0.4.0 (2020-10-16) -### Changed -- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#177]) - -[#177]: https://github.com/RustCrypto/stream-ciphers/pull/177 - -## 0.3.0 (2020-08-25) -### Changed -- Bump `stream-cipher` dependency to v0.7, implement the `FromBlockCipher` trait ([#161], [#164]) - -[#161]: https://github.com/RustCrypto/stream-ciphers/pull/161 -[#164]: https://github.com/RustCrypto/stream-ciphers/pull/164 - -## 0.2.0 (2020-06-08) -### Changed -- Bump `stream-cipher` dependency to v0.4 ([#123]) -- Upgrade to Rust 2018 edition ([#123]) - -[#123]: https://github.com/RustCrypto/stream-ciphers/pull/123 - -## 0.1.1 (2019-03-11) - -## 0.1.0 (2018-12-26) diff --git a/ofb/Cargo.toml b/ofb/Cargo.toml deleted file mode 100644 index 9b5cbc0d..00000000 --- a/ofb/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "ofb" -version = "0.5.1" # Also update html_root_url in lib.rs when bumping this -authors = ["RustCrypto Developers"] -license = "MIT OR Apache-2.0" -description = "Generic Output Feedback (OFB) mode implementation." -documentation = "https://docs.rs/ofb" -repository = "https://github.com/RustCrypto/stream-ciphers" -keywords = ["crypto", "stream-cipher", "block-mode"] -categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" - -[dependencies] -cipher = "0.3" - -[dev-dependencies] -aes = { version = "0.7", features = ["force-soft"] } # Uses `force-soft` for MSRV 1.41 -cipher = { version = "0.3", features = ["dev"] } -hex-literal = "0.2" diff --git a/ofb/LICENSE-APACHE b/ofb/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/ofb/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/ofb/LICENSE-MIT b/ofb/LICENSE-MIT deleted file mode 100644 index f5b157a6..00000000 --- a/ofb/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 Artyom Pavlov - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ofb/README.md b/ofb/README.md deleted file mode 100644 index bed90c0e..00000000 --- a/ofb/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# RustCrypto: Output Feedback Mode (OFB) - -[![Crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] -[![HAZMAT][hazmat-image]][hazmat-link] - -Generic [Output Feedback (OFB)][1] mode implementation as a -[synchronous stream cipher][2]. - -[Documentation][docs-link] - -diagram - -## ⚠️ Security Warning: [Hazmat!][hazmat-link] - -This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to -verify ciphertext integrity), which can lead to serious vulnerabilities -if used incorrectly! - -No security audits of this crate have ever been performed, and it has not been -thoroughly assessed to ensure its operation is constant-time on common CPU -architectures. - -USE AT YOUR OWN RISK! - -## Minimum Supported Rust Version - -Rust **1.41** or higher. - -Minimum supported Rust version can be changed in the future, but it will be -done with a minor version bump. - -## SemVer Policy - -- All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/ofb.svg -[crate-link]: https://crates.io/crates/ofb -[docs-image]: https://docs.rs/ofb/badge.svg -[docs-link]: https://docs.rs/ofb/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers -[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg -[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md -[build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/ofb/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/stream-ciphers/actions?query=workflow%3Aofb - -[//]: # (footnotes) - -[1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#OFB -[2]: https://en.wikipedia.org/wiki/Stream_cipher#Synchronous_stream_ciphers diff --git a/ofb/benches/cfb-mode.rs b/ofb/benches/cfb-mode.rs deleted file mode 100644 index facbf1e7..00000000 --- a/ofb/benches/cfb-mode.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_sync_bench!(ofb::Ofb); diff --git a/ofb/src/lib.rs b/ofb/src/lib.rs deleted file mode 100644 index f7808dac..00000000 --- a/ofb/src/lib.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Generic [Output Feedback (OFB)][1] mode implementation. -//! -//! This crate implements OFB as a [synchronous stream cipher][2]. -//! -//! # Security Warning -//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity -//! is not verified, which can lead to serious vulnerabilities! -//! -//! # Examples -//! ``` -//! use aes::Aes128; -//! use hex_literal::hex; -//! use ofb::Ofb; -//! use ofb::cipher::{NewCipher, StreamCipher}; -//! -//! type AesOfb = Ofb; -//! -//! let key = b"very secret key."; -//! let iv = b"unique init vect"; -//! let plaintext = b"The quick brown fox jumps over the lazy dog."; -//! let ciphertext = hex!(" -//! 8f0cb6e8 9286cd02 09c95da4 fa663269 -//! 9455b0bb e346b653 ec0d44aa bece8cc9 -//! f886df67 049d780d 9ccdf957 -//! "); -//! -//! let mut buffer = plaintext.to_vec(); -//! // create cipher instance -//! let mut cipher = AesOfb::new_from_slices(key, iv).unwrap(); -//! // apply keystream (encrypt) -//! cipher.apply_keystream(&mut buffer); -//! assert_eq!(buffer, &ciphertext[..]); -//! // and decrypt it back -//! AesOfb::new_from_slices(key, iv).unwrap().apply_keystream(&mut buffer); -//! assert_eq!(buffer, &plaintext[..]); -//! -//! // OFB mode can be used with streaming messages -//! let mut cipher = AesOfb::new_from_slices(key, iv).unwrap(); -//! for chunk in buffer.chunks_mut(3) { -//! cipher.apply_keystream(chunk); -//! } -//! assert_eq!(buffer, &ciphertext[..]); -//! ``` -//! -//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#OFB -//! [2]: https://en.wikipedia.org/wiki/Stream_cipher#Synchronous_stream_ciphers - -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/ofb/0.5.1" -)] -#![forbid(unsafe_code)] -#![warn(missing_docs, rust_2018_idioms)] - -pub use cipher; - -use cipher::{ - errors::LoopError, - generic_array::{typenum::Unsigned, GenericArray}, - Block, BlockCipher, BlockEncrypt, FromBlockCipher, StreamCipher, -}; - -/// OFB self-synchronizing stream cipher instance. -pub struct Ofb { - cipher: C, - block: Block, - pos: usize, -} - -impl FromBlockCipher for Ofb -where - C: BlockCipher + BlockEncrypt, -{ - type BlockCipher = C; - type NonceSize = C::BlockSize; - - fn from_block_cipher(cipher: C, iv: &GenericArray) -> Self { - let mut block = iv.clone(); - cipher.encrypt_block(&mut block); - Self { - cipher, - block, - pos: 0, - } - } -} - -impl StreamCipher for Ofb { - fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { - let bs = C::BlockSize::to_usize(); - let n = data.len(); - - if n < bs - self.pos { - xor(data, &self.block[self.pos..self.pos + n]); - self.pos += n; - return Ok(()); - } - - let (left, right) = { data }.split_at_mut(bs - self.pos); - data = right; - let mut block = self.block.clone(); - xor(left, &block[self.pos..]); - self.cipher.encrypt_block(&mut block); - - let mut chunks = data.chunks_exact_mut(bs); - for chunk in &mut chunks { - xor(chunk, &block); - self.cipher.encrypt_block(&mut block); - } - - let rem = chunks.into_remainder(); - xor(rem, &block[..rem.len()]); - self.block = block; - self.pos = rem.len(); - - Ok(()) - } -} - -#[inline(always)] -fn xor(buf1: &mut [u8], buf2: &[u8]) { - debug_assert_eq!(buf1.len(), buf2.len()); - for (a, b) in buf1.iter_mut().zip(buf2) { - *a ^= *b; - } -} diff --git a/ofb/tests/data/aes128.blb b/ofb/tests/data/aes128.blb deleted file mode 100644 index 76169d0e..00000000 Binary files a/ofb/tests/data/aes128.blb and /dev/null differ diff --git a/ofb/tests/lib.rs b/ofb/tests/lib.rs deleted file mode 100644 index 524f3d6a..00000000 --- a/ofb/tests/lib.rs +++ /dev/null @@ -1 +0,0 @@ -cipher::stream_cipher_test!(ofb_aes128, ofb::Ofb, "aes128"); diff --git a/rabbit/CHANGELOG.md b/rabbit/CHANGELOG.md index 590b0fe0..652f3eee 100644 --- a/rabbit/CHANGELOG.md +++ b/rabbit/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.0 (2022-02-10) +### Changed +- Bump `cipher` dependency to v0.4 ([#276]) + +[#276]: https://github.com/RustCrypto/stream-ciphers/pull/276 + ## 0.3.1 (2021-07-20) ### Changed - Pin `zeroize` dependency to v1.3 ([#256]) diff --git a/rabbit/Cargo.toml b/rabbit/Cargo.toml index df49ce3d..9ea27b4f 100644 --- a/rabbit/Cargo.toml +++ b/rabbit/Cargo.toml @@ -1,18 +1,28 @@ [package] name = "rabbit" -version = "0.3.1" # Also update html_root_url in lib.rs when bumping this +version = "0.4.0" # Also update html_root_url in lib.rs when bumping this description = "An implementation of the Rabbit Stream Cipher Algorithm" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/rabbit" repository = "https://github.com/RustCrypto/stream-ciphers" keywords = ["crypto", "rabbit", "stream-cipher", "trait"] categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" [dependencies] -cipher = "0.3" -zeroize = { version = "=1.3", optional = true, default-features = false, features = ["zeroize_derive"] } +cipher = "0.4" + +[dev-dependencies] +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3" [features] -default = [] +std = ["cipher/std"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/rabbit/README.md b/rabbit/README.md index 0c44261a..5342cd46 100644 --- a/rabbit/README.md +++ b/rabbit/README.md @@ -26,7 +26,7 @@ architectures. ## Minimum Supported Rust Version -Rust **1.41** or higher. +Rust **1.56** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -58,7 +58,7 @@ dual licensed as above, without any additional terms or conditions. [docs-image]: https://docs.rs/rabbit/badge.svg [docs-link]: https://docs.rs/rabbit/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers [build-image]: https://github.com/RustCrypto/stream-ciphers/workflows/rabbit/badge.svg?branch=master&event=push diff --git a/rabbit/benches/mod.rs b/rabbit/benches/mod.rs new file mode 100644 index 00000000..78bd07eb --- /dev/null +++ b/rabbit/benches/mod.rs @@ -0,0 +1,10 @@ +#![feature(test)] +extern crate test; + +cipher::stream_cipher_bench!( + rabbit::Rabbit; + rabbit_bench1_16b 16; + rabbit_bench2_256b 256; + rabbit_bench3_1kib 1024; + rabbit_bench4_16kib 16384; +); diff --git a/rabbit/benches/rabbit.rs b/rabbit/benches/rabbit.rs deleted file mode 100644 index 8d7ce8ad..00000000 --- a/rabbit/benches/rabbit.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_sync_bench!(rabbit::Rabbit); diff --git a/rabbit/src/lib.rs b/rabbit/src/lib.rs index f7c8774d..25d49174 100644 --- a/rabbit/src/lib.rs +++ b/rabbit/src/lib.rs @@ -1,12 +1,59 @@ -//! This crate implements the Rabbit Stream Cipher Algorithm as described in [RFC 4503][1] +//! Implementation of the [Rabbit] stream cipher. //! -//! [1]: https://tools.ietf.org/html/rfc4503#section-2.3 +//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate. +//! +//! # ⚠️ Security Warning: Hazmat! +//! +//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity +//! is not verified, which can lead to serious vulnerabilities! +//! +//! USE AT YOUR OWN RISK! +//! +//! # Example +//! ``` +//! use rabbit::Rabbit; +//! // Import relevant traits +//! use rabbit::cipher::{KeyIvInit, StreamCipher}; +//! use hex_literal::hex; +//! +//! let key = [0x42; 16]; +//! let nonce = [0x24; 8]; +//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F"); +//! let ciphertext = hex!("10298496 ceda18ee 0e257cbb 1ab43bcc"); +//! +//! // Key and IV must be references to the `GenericArray` type. +//! // Here we use the `Into` trait to convert arrays into it. +//! let mut cipher = Rabbit::new(&key.into(), &nonce.into()); +//! +//! let mut buffer = plaintext.clone(); +//! +//! // apply keystream (encrypt) +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, ciphertext); +//! +//! let ciphertext = buffer.clone(); +//! +//! // decrypt ciphertext by applying keystream again +//! let mut cipher = Rabbit::new(&key.into(), &nonce.into()); +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, plaintext); +//! +//! // stream ciphers can be used with streaming messages +//! let mut cipher = Rabbit::new(&key.into(), &nonce.into()); +//! for chunk in buffer.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buffer, ciphertext); +//! ``` +//! +//! [Rabbit]: https://tools.ietf.org/html/rfc4503#section-2.3 #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/rabbit/0.3.1" + html_root_url = "https://docs.rs/rabbit/0.4.0" )] #![deny(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] @@ -14,645 +61,302 @@ pub use cipher; use cipher::{ - consts::{U16, U8}, - errors::LoopError, - NewCipher, StreamCipher, + consts::{U1, U16, U8}, + crypto_common::InnerUser, + Block, BlockSizeUser, InnerIvInit, IvSizeUser, KeyInit, KeySizeUser, ParBlocksSizeUser, + StreamBackend, StreamCipherCore, StreamCipherCoreWrapper, StreamClosure, }; -#[cfg(feature = "zeroize")] -use zeroize::Zeroize; -use core::{cmp::min, fmt, mem::replace}; +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; /// RFC 4503. 2.3. Key Setup Scheme (page 2). -pub const KEY_BYTE_LEN: usize = 16; +const KEY_BYTE_LEN: usize = 16; /// RFC 4503. 2.4. IV Setup Scheme (page 2-3). -pub const IV_BYTE_LEN: usize = 8; +const IV_BYTE_LEN: usize = 8; /// RFC 4503. 2.1. Notation (page 2). const WORDSIZE: u64 = 1 << 32; -const MESSAGE_BLOCK_BYTE_LEN: usize = 16; - /// RFC 4503. 2.5. Counter System (page 3). const A: [u32; 8] = [ 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3, 0x34D34D34, 0x4D34D34D, 0xD34D34D3, ]; /// Rabbit Stream Cipher Key. -pub type Key = cipher::CipherKey; - -/// Rabbit Stream Cipher Initialization Vector. See RFC 4503 3.2. Initialization Vector (page 5). -/// -/// > It is possible to run Rabbit without the IV setup. However, in this -/// > case, the generator must never be reset under the same key, since -/// > this would destroy its security (for a recent example, see [4]). -/// > However, in order to guarantee synchronization between sender and -/// > receiver, ciphers are frequently reset in practice. This means that -/// > both sender and receiver set the inner state of the cipher back to a -/// > known value and then derive the new encryption state using an IV. If -/// > this is done, it is important to make sure that no IV is ever reused -/// > under the same key. -/// -/// [4]: http://eprint.iacr.org/2005/007.pdf -pub type Iv = cipher::Nonce; +pub type Key = cipher::Key; + +/// Rabbit Stream Cipher Initialization Vector. +pub type Iv = cipher::Iv; + +type BlockSize = U16; + +/// The Rabbit stream cipher initializied only with key. +pub type RabbitKeyOnly = StreamCipherCoreWrapper; +/// The Rabbit stream cipher initializied with key and IV. +pub type Rabbit = StreamCipherCoreWrapper; /// RFC 4503. 2.2. Inner State (page 2). -#[derive(Default, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "zeroize", derive(Zeroize))] -#[cfg_attr(feature = "zeroize", zeroize(drop))] +#[derive(Clone)] struct State { - state_vars: [u32; 8], - counter_vars: [u32; 8], + /// State variables + x: [u32; 8], + /// Counter variables + c: [u32; 8], carry_bit: u8, } -impl fmt::Debug for State { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("State { ... }") - } -} +impl State { + /// RFC 4503. 2.3. Key Setup Scheme (page 2). + fn setup_key(key: [u8; KEY_BYTE_LEN]) -> Self { + let mut k = [0u16; 8]; + + k[0] = (key[0x0] as u16) | ((key[0x1] as u16) << 8); + k[1] = (key[0x2] as u16) | ((key[0x3] as u16) << 8); + k[2] = (key[0x4] as u16) | ((key[0x5] as u16) << 8); + k[3] = (key[0x6] as u16) | ((key[0x7] as u16) << 8); + k[4] = (key[0x8] as u16) | ((key[0x9] as u16) << 8); + k[5] = (key[0xA] as u16) | ((key[0xB] as u16) << 8); + k[6] = (key[0xC] as u16) | ((key[0xD] as u16) << 8); + k[7] = (key[0xE] as u16) | ((key[0xF] as u16) << 8); + + let mut x = [0u32; 8]; + let mut c = [0u32; 8]; + for j in 0..8 { + if j % 2 == 0 { + x[j] = ((k[(j + 1) % 8] as u32) << 16) | (k[j] as u32); + c[j] = ((k[(j + 4) % 8] as u32) << 16) | (k[(j + 5) % 8] as u32); + } else { + x[j] = ((k[(j + 5) % 8] as u32) << 16) | (k[(j + 4) % 8] as u32); + c[j] = ((k[j] as u32) << 16) | (k[(j + 1) % 8] as u32); + } + } -/// RFC 4503. 2.3. Key Setup Scheme (page 2). -fn setup_key(state: &mut State, key: [u8; KEY_BYTE_LEN]) { - let mut k = [0u16; 8]; - - k[0] = (key[0x0] as u16) | ((key[0x1] as u16) << 8); - k[1] = (key[0x2] as u16) | ((key[0x3] as u16) << 8); - k[2] = (key[0x4] as u16) | ((key[0x5] as u16) << 8); - k[3] = (key[0x6] as u16) | ((key[0x7] as u16) << 8); - k[4] = (key[0x8] as u16) | ((key[0x9] as u16) << 8); - k[5] = (key[0xA] as u16) | ((key[0xB] as u16) << 8); - k[6] = (key[0xC] as u16) | ((key[0xD] as u16) << 8); - k[7] = (key[0xE] as u16) | ((key[0xF] as u16) << 8); - - for j in 0..8 { - if j % 2 == 0 { - state.state_vars[j] = ((k[(j + 1) % 8] as u32) << 16) | (k[j] as u32); - state.counter_vars[j] = ((k[(j + 4) % 8] as u32) << 16) | (k[(j + 5) % 8] as u32); - } else { - state.state_vars[j] = ((k[(j + 5) % 8] as u32) << 16) | (k[(j + 4) % 8] as u32); - state.counter_vars[j] = ((k[j] as u32) << 16) | (k[(j + 1) % 8] as u32); + let carry_bit = 0; + let mut state = Self { x, c, carry_bit }; + + for _ in 0..4 { + state.next_state(); } - } - #[cfg(feature = "zeroize")] - k.zeroize(); + for j in 0..8 { + state.c[j] ^= state.x[(j + 4) % 8]; + } - for _ in 0..4 { - next_state(state); + state } - for j in 0..8 { - state.counter_vars[j] ^= state.state_vars[(j + 4) % 8]; + /// RFC 4503. 2.4. IV Setup Scheme (page 2-3). + fn setup_iv(&mut self, iv: [u8; IV_BYTE_LEN]) { + let mut i = [0_u32; 4]; + + i[0] = iv[0] as u32 | (iv[1] as u32) << 8 | (iv[2] as u32) << 16 | (iv[3] as u32) << 24; + i[2] = iv[4] as u32 | (iv[5] as u32) << 8 | (iv[6] as u32) << 16 | (iv[7] as u32) << 24; + i[1] = (i[0] >> 16) | (i[2] & 0xFFFF0000); + i[3] = (i[2] << 16) | (i[0] & 0x0000FFFF); + + self.c[0] ^= i[0]; + self.c[1] ^= i[1]; + self.c[2] ^= i[2]; + self.c[3] ^= i[3]; + self.c[4] ^= i[0]; + self.c[5] ^= i[1]; + self.c[6] ^= i[2]; + self.c[7] ^= i[3]; + + for _ in 0..4 { + self.next_state(); + } } -} -/// RFC 4503. 2.4. IV Setup Scheme (page 2-3). -fn setup_iv(state: &mut State, iv: [u8; IV_BYTE_LEN]) { - let mut i = [0_u32; 4]; - - i[0] = iv[0] as u32 | (iv[1] as u32) << 8 | (iv[2] as u32) << 16 | (iv[3] as u32) << 24; - i[2] = iv[4] as u32 | (iv[5] as u32) << 8 | (iv[6] as u32) << 16 | (iv[7] as u32) << 24; - i[1] = (i[0] >> 16) | (i[2] & 0xFFFF0000); - i[3] = (i[2] << 16) | (i[0] & 0x0000FFFF); - - state.counter_vars[0] ^= i[0]; - state.counter_vars[1] ^= i[1]; - state.counter_vars[2] ^= i[2]; - state.counter_vars[3] ^= i[3]; - state.counter_vars[4] ^= i[0]; - state.counter_vars[5] ^= i[1]; - state.counter_vars[6] ^= i[2]; - state.counter_vars[7] ^= i[3]; - - #[cfg(feature = "zeroize")] - i.zeroize(); - - for _ in 0..4 { - next_state(state); + /// RFC 4503. 2.5. Counter System (page 3). + fn counter_update(&mut self) { + #[allow(unused_mut, clippy::needless_range_loop)] + for j in 0..8 { + let t = self.c[j] as u64 + A[j] as u64 + self.carry_bit as u64; + self.carry_bit = ((t / WORDSIZE) as u8) & 0b1; + self.c[j] = (t % WORDSIZE) as u32; + } } -} -/// RFC 4503. 2.5. Counter System (page 3). -fn counter_update(state: &mut State) { - #[allow(unused_mut, clippy::needless_range_loop)] - for j in 0..8 { - let mut temp = state.counter_vars[j] as u64 + A[j] as u64 + state.carry_bit as u64; - state.carry_bit = ((temp / WORDSIZE) as u8) & 0b1; - state.counter_vars[j] = (temp % WORDSIZE) as u32; - #[cfg(feature = "zeroize")] - temp.zeroize(); - } -} + /// RFC 4503. 2.6. Next-State Function (page 3-4). + fn next_state(&mut self) { + let mut g = [0u32; 8]; -/// RFC 4503. 2.6. Next-State Function (page 3-4). -fn next_state(state: &mut State) { - let mut g = [0u32; 8]; + self.counter_update(); - counter_update(state); + #[allow(clippy::needless_range_loop)] + for j in 0..8 { + let u_plus_v = self.x[j] as u64 + self.c[j] as u64; + let square_uv = (u_plus_v % WORDSIZE) * (u_plus_v % WORDSIZE); + g[j] = (square_uv ^ (square_uv >> 32)) as u32; + } + + self.x[0] = g[0] + .wrapping_add(g[7].rotate_left(16)) + .wrapping_add(g[6].rotate_left(16)); + self.x[1] = g[1].wrapping_add(g[0].rotate_left(8)).wrapping_add(g[7]); + self.x[2] = g[2] + .wrapping_add(g[1].rotate_left(16)) + .wrapping_add(g[0].rotate_left(16)); + self.x[3] = g[3].wrapping_add(g[2].rotate_left(8)).wrapping_add(g[1]); + self.x[4] = g[4] + .wrapping_add(g[3].rotate_left(16)) + .wrapping_add(g[2].rotate_left(16)); + self.x[5] = g[5].wrapping_add(g[4].rotate_left(8)).wrapping_add(g[3]); + self.x[6] = g[6] + .wrapping_add(g[5].rotate_left(16)) + .wrapping_add(g[4].rotate_left(16)); + self.x[7] = g[7].wrapping_add(g[6].rotate_left(8)).wrapping_add(g[5]); + } - #[allow(clippy::needless_range_loop)] - for j in 0..8 { - let u_plus_v = state.state_vars[j] as u64 + state.counter_vars[j] as u64; - let square_uv = (u_plus_v % WORDSIZE) * (u_plus_v % WORDSIZE); - g[j] = (square_uv ^ (square_uv >> 32)) as u32; + /// RFC 4503. 2.7. Extraction Scheme (page 4). + fn extract(&self) -> [u8; 16] { + let mut s = [0u8; 16]; + + let mut tmp = [0_u16; 8]; + + tmp[0] = ((self.x[0]) ^ (self.x[5] >> 16)) as u16; + tmp[1] = ((self.x[0] >> 16) ^ (self.x[3])) as u16; + tmp[2] = ((self.x[2]) ^ (self.x[7] >> 16)) as u16; + tmp[3] = ((self.x[2] >> 16) ^ (self.x[5])) as u16; + tmp[4] = ((self.x[4]) ^ (self.x[1] >> 16)) as u16; + tmp[5] = ((self.x[4] >> 16) ^ (self.x[7])) as u16; + tmp[6] = ((self.x[6]) ^ (self.x[3] >> 16)) as u16; + tmp[7] = ((self.x[6] >> 16) ^ (self.x[1])) as u16; + + s[0x0] = tmp[0] as u8; + s[0x1] = (tmp[0] >> 8) as u8; + s[0x2] = tmp[1] as u8; + s[0x3] = (tmp[1] >> 8) as u8; + s[0x4] = tmp[2] as u8; + s[0x5] = (tmp[2] >> 8) as u8; + s[0x6] = tmp[3] as u8; + s[0x7] = (tmp[3] >> 8) as u8; + s[0x8] = tmp[4] as u8; + s[0x9] = (tmp[4] >> 8) as u8; + s[0xA] = tmp[5] as u8; + s[0xB] = (tmp[5] >> 8) as u8; + s[0xC] = tmp[6] as u8; + s[0xD] = (tmp[6] >> 8) as u8; + s[0xE] = tmp[7] as u8; + s[0xF] = (tmp[7] >> 8) as u8; + + s } - state.state_vars[0] = g[0] - .wrapping_add(g[7].rotate_left(16)) - .wrapping_add(g[6].rotate_left(16)); - state.state_vars[1] = g[1].wrapping_add(g[0].rotate_left(8)).wrapping_add(g[7]); - state.state_vars[2] = g[2] - .wrapping_add(g[1].rotate_left(16)) - .wrapping_add(g[0].rotate_left(16)); - state.state_vars[3] = g[3].wrapping_add(g[2].rotate_left(8)).wrapping_add(g[1]); - state.state_vars[4] = g[4] - .wrapping_add(g[3].rotate_left(16)) - .wrapping_add(g[2].rotate_left(16)); - state.state_vars[5] = g[5].wrapping_add(g[4].rotate_left(8)).wrapping_add(g[3]); - state.state_vars[6] = g[6] - .wrapping_add(g[5].rotate_left(16)) - .wrapping_add(g[4].rotate_left(16)); - state.state_vars[7] = g[7].wrapping_add(g[6].rotate_left(8)).wrapping_add(g[5]); - - #[cfg(feature = "zeroize")] - g.zeroize(); + fn next_block(&mut self) -> [u8; 16] { + self.next_state(); + self.extract() + } } -/// RFC 4503. 2.7. Extraction Scheme (page 4). -fn extract(state: &State) -> [u8; 16] { - let mut s = [0u8; 16]; - - let mut tmp = [0_u16; 8]; - - tmp[0] = ((state.state_vars[0]) ^ (state.state_vars[5] >> 16)) as u16; - tmp[1] = ((state.state_vars[0] >> 16) ^ (state.state_vars[3])) as u16; - tmp[2] = ((state.state_vars[2]) ^ (state.state_vars[7] >> 16)) as u16; - tmp[3] = ((state.state_vars[2] >> 16) ^ (state.state_vars[5])) as u16; - tmp[4] = ((state.state_vars[4]) ^ (state.state_vars[1] >> 16)) as u16; - tmp[5] = ((state.state_vars[4] >> 16) ^ (state.state_vars[7])) as u16; - tmp[6] = ((state.state_vars[6]) ^ (state.state_vars[3] >> 16)) as u16; - tmp[7] = ((state.state_vars[6] >> 16) ^ (state.state_vars[1])) as u16; - - s[0x0] = tmp[0] as u8; - s[0x1] = (tmp[0] >> 8) as u8; - s[0x2] = tmp[1] as u8; - s[0x3] = (tmp[1] >> 8) as u8; - s[0x4] = tmp[2] as u8; - s[0x5] = (tmp[2] >> 8) as u8; - s[0x6] = tmp[3] as u8; - s[0x7] = (tmp[3] >> 8) as u8; - s[0x8] = tmp[4] as u8; - s[0x9] = (tmp[4] >> 8) as u8; - s[0xA] = tmp[5] as u8; - s[0xB] = (tmp[5] >> 8) as u8; - s[0xC] = tmp[6] as u8; - s[0xD] = (tmp[6] >> 8) as u8; - s[0xE] = tmp[7] as u8; - s[0xF] = (tmp[7] >> 8) as u8; - - #[cfg(feature = "zeroize")] - tmp.zeroize(); - - s +#[cfg(feature = "zeroize")] +impl core::ops::Drop for State { + fn drop(&mut self) { + self.x.zeroize(); + self.c.zeroize(); + self.carry_bit.zeroize(); + } } -/// Rabbit stream cipher state. -#[cfg_attr(feature = "zeroize", derive(Zeroize))] -#[cfg_attr(feature = "zeroize", zeroize(drop))] -pub struct Rabbit { - master_state: State, +/// Core state of the Rabbit stream cipher initialized only with key. +#[derive(Clone)] +pub struct RabbitKeyOnlyCore { state: State, - block: [u8; 16], - block_idx: usize, - block_num: u64, } -impl fmt::Debug for Rabbit { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Rabbit { ... }") - } +impl KeySizeUser for RabbitKeyOnlyCore { + type KeySize = U16; } -impl Rabbit { - /// Creates an empty rabbit state, then setups the given `key` on it. - /// - /// See RFC 4503 3.2. Initialization Vector (page 5). - #[allow(unused_mut)] - pub fn setup_without_iv(mut key: [u8; KEY_BYTE_LEN]) -> Rabbit { - let mut master_state = Default::default(); - setup_key(&mut master_state, key); - #[cfg(feature = "zeroize")] - key.zeroize(); - - let mut state = master_state.clone(); - next_state(&mut state); - Rabbit { - master_state, - block: extract(&state), - state, - block_idx: 0, - block_num: 0, +impl KeyInit for RabbitKeyOnlyCore { + fn new(key: &Key) -> Self { + Self { + state: State::setup_key((*key).into()), } } +} - /// Creates an empty rabbit state, then setups the given `key` and `iv` on it. - #[allow(unused_mut)] - pub fn setup(key: [u8; KEY_BYTE_LEN], mut iv: [u8; IV_BYTE_LEN]) -> Rabbit { - let mut this = Self::setup_without_iv(key); - this.state = this.master_state.clone(); - setup_iv(&mut this.state, iv); - #[cfg(feature = "zeroize")] - iv.zeroize(); - - next_state(&mut this.state); - this.block = extract(&this.state); - this - } - - /// Restores master state (iv will be lost). - pub fn reset(&mut self) { - self.state = self.master_state.clone(); - next_state(&mut self.state); - self.block = extract(&self.state); - self.block_idx = 0; - self.block_num = 0; - } - - /// Restores master state, than setups initialization vector `iv` on it. - #[allow(unused_mut)] - pub fn reinit(&mut self, mut iv: [u8; IV_BYTE_LEN]) { - self.state = self.master_state.clone(); - setup_iv(&mut self.state, iv); - #[cfg(feature = "zeroize")] - iv.zeroize(); - - next_state(&mut self.state); - self.block = extract(&self.state); - self.block_idx = 0; - self.block_num = 0; - } - - /// Encrypts bytes of `data` inplace. - /// - /// Returns: - /// - /// * `true` – OK; - /// * `false` – max message length (16 * 2⁶⁴ bytes) was exceeded. `data` is not affected. - pub fn encrypt_inplace(&mut self, data: &mut [u8]) -> bool { - if !self.check_keystream_len(data.len()) { - return false; - } - - let prefix_len = min( - (MESSAGE_BLOCK_BYTE_LEN - (self.block_idx as usize)) % MESSAGE_BLOCK_BYTE_LEN, - data.len(), - ); - let num_blocks = (data.len() - prefix_len) / MESSAGE_BLOCK_BYTE_LEN; - let suffix_len = (data.len() - prefix_len) % MESSAGE_BLOCK_BYTE_LEN; - - let mut i = 0; - let mut block_buf = [0_u8; MESSAGE_BLOCK_BYTE_LEN]; - - for _ in 0..prefix_len { - data[i] ^= self.get_s_byte(); - i += 1; - } - - for _ in 0..num_blocks { - block_buf.copy_from_slice(&data[i..i + MESSAGE_BLOCK_BYTE_LEN]); - - let lhs = u128::from_le_bytes(block_buf); - let mut rhs = u128::from_le_bytes(self.get_s_block()); - - rhs ^= lhs; - - (&mut data[i..i + MESSAGE_BLOCK_BYTE_LEN]).copy_from_slice(&rhs.to_le_bytes()); - - i += MESSAGE_BLOCK_BYTE_LEN; - } - - #[cfg(feature = "zeroize")] - block_buf.zeroize(); - - for _ in 0..suffix_len { - data[i] ^= self.get_s_byte(); - i += 1; - } - - true - } +impl BlockSizeUser for RabbitKeyOnlyCore { + type BlockSize = BlockSize; +} - /// Decrypts bytes of `data` inplace (see [`Rabbit::encrypt_inplace`]). +impl StreamCipherCore for RabbitKeyOnlyCore { #[inline(always)] - pub fn decrypt_inplace(&mut self, data: &mut [u8]) -> bool { - self.encrypt_inplace(data) + fn remaining_blocks(&self) -> Option { + // Rabbit can generate 2^64 blocks, but since it does not implement + // the seeking traits, we can assume that so many blocks never will + // be processed + None } - /// Returns `true` if keystream length is enough to encrypt `required` number of bytes. - fn check_keystream_len(&self, required: usize) -> bool { - let blocks_required = required / MESSAGE_BLOCK_BYTE_LEN; - let blocks_remainig = u64::max_value() - self.block_num; - match blocks_remainig.cmp(&(blocks_required as u64)) { - core::cmp::Ordering::Greater => true, - core::cmp::Ordering::Equal => { - let bytes_required = required % MESSAGE_BLOCK_BYTE_LEN; - let bytes_remining = 0x10 - self.block_idx; - match bytes_remining.cmp(&bytes_required) { - core::cmp::Ordering::Equal | core::cmp::Ordering::Greater => true, - core::cmp::Ordering::Less => false, - } - } - core::cmp::Ordering::Less => false, - } + fn process_with_backend(&mut self, f: impl StreamClosure) { + f.call(&mut Backend(&mut self.state)); } +} - /// Will consume the next byte of the keystream. The keystream will be moved one byte further. - /// - /// # Security Considerations - /// - /// Make sure to call this only if there is enough bytes in the keystream - /// (see [`Rabbit::check_keystream_len`], RFC 4503 3.1. Message Length (page 5)). - fn get_s_byte(&mut self) -> u8 { - let byte = self.block[self.block_idx as usize]; - - self.block_idx = (self.block_idx + 1) % MESSAGE_BLOCK_BYTE_LEN; - if self.block_idx == 0 { - #[cfg(feature = "zeroize")] - self.block.zeroize(); - next_state(&mut self.state); - self.block = extract(&self.state); - self.block_num += 1; - } - - byte - } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for RabbitKeyOnlyCore {} - /// Will consume the next block of the keystream. The keystream will be moved one block further. - /// - /// # Requires - /// - /// * `self.buf_idx == 0` - /// - /// # Security Considerations - /// - /// Make sure to call this only if there is enough bytes in the keystream - /// (see [`Rabbit::check_keystream_len`], RFC 4503 3.1. Message Length (page 5)). - fn get_s_block(&mut self) -> [u8; 16] { - debug_assert_eq!(self.block_idx, 0, "Block is partially consumed"); - next_state(&mut self.state); - self.block_num += 1; - replace(&mut self.block, extract(&self.state)) - } +/// Core state of the Rabbit stream cipher initialized with key and IV. +#[derive(Clone)] +pub struct RabbitCore { + state: State, } -impl NewCipher for Rabbit { - type KeySize = U16; - type NonceSize = U8; +impl InnerUser for RabbitCore { + type Inner = RabbitKeyOnlyCore; +} - fn new(key: &cipher::CipherKey, iv: &cipher::Nonce) -> Self { - Self::setup((*key).into(), (*iv).into()) - } +impl IvSizeUser for RabbitCore { + type IvSize = U8; } -impl StreamCipher for Rabbit { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - if self.encrypt_inplace(data) { - Ok(()) - } else { - Err(LoopError) - } +impl InnerIvInit for RabbitCore { + fn inner_iv_init(inner: RabbitKeyOnlyCore, iv: &Iv) -> Self { + let mut state = inner.state; + state.setup_iv((*iv).into()); + Self { state } } } -#[cfg(test)] -mod test { - use super::*; - - macro_rules! test_raw { - ($name:ident $wrap_name:ident $stream_name:ident - key = [$kf:expr, $ke:expr, $kd:expr, $kc:expr, - $kb:expr, $ka:expr, $k9:expr, $k8:expr, - $k7:expr, $k6:expr, $k5:expr, $k4:expr, - $k3:expr, $k2:expr, $k1:expr, $k0:expr] - S[0] = [$s0f:expr, $s0e:expr, $s0d:expr, $s0c:expr, - $s0b:expr, $s0a:expr, $s09:expr, $s08:expr, - $s07:expr, $s06:expr, $s05:expr, $s04:expr, - $s03:expr, $s02:expr, $s01:expr, $s00:expr] - S[1] = [$s1f:expr, $s1e:expr, $s1d:expr, $s1c:expr, - $s1b:expr, $s1a:expr, $s19:expr, $s18:expr, - $s17:expr, $s16:expr, $s15:expr, $s14:expr, - $s13:expr, $s12:expr, $s11:expr, $s10:expr] - S[2] = [$s2f:expr, $s2e:expr, $s2d:expr, $s2c:expr, - $s2b:expr, $s2a:expr, $s29:expr, $s28:expr, - $s27:expr, $s26:expr, $s25:expr, $s24:expr, - $s23:expr, $s22:expr, $s21:expr, $s20:expr]) => { - #[test] - fn $name() { - let key = [ - $k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7, $k8, $k9, $ka, $kb, $kc, $kd, $ke, $kf, - ]; - let s0 = [ - $s00, $s01, $s02, $s03, $s04, $s05, $s06, $s07, $s08, $s09, $s0a, $s0b, $s0c, - $s0d, $s0e, $s0f, - ]; - let s1 = [ - $s10, $s11, $s12, $s13, $s14, $s15, $s16, $s17, $s18, $s19, $s1a, $s1b, $s1c, - $s1d, $s1e, $s1f, - ]; - let s2 = [ - $s20, $s21, $s22, $s23, $s24, $s25, $s26, $s27, $s28, $s29, $s2a, $s2b, $s2c, - $s2d, $s2e, $s2f, - ]; - let mut state = Default::default(); - setup_key(&mut state, key); - next_state(&mut state); - assert_eq!(extract(&state), s0); - next_state(&mut state); - assert_eq!(extract(&state), s1); - next_state(&mut state); - assert_eq!(extract(&state), s2); - } - - #[test] - fn $wrap_name() { - let key = [ - $k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7, $k8, $k9, $ka, $kb, $kc, $kd, $ke, $kf, - ]; - let s = [ - $s00, $s01, $s02, $s03, $s04, $s05, $s06, $s07, $s08, $s09, $s0a, $s0b, $s0c, - $s0d, $s0e, $s0f, $s10, $s11, $s12, $s13, $s14, $s15, $s16, $s17, $s18, $s19, - $s1a, $s1b, $s1c, $s1d, $s1e, $s1f, $s20, $s21, $s22, $s23, $s24, $s25, $s26, - $s27, $s28, $s29, $s2a, $s2b, $s2c, $s2d, $s2e, $s2f, - ]; - - let mut d; - - for n in 0..48 { - let s = &s[0..n]; - - d = [0; 48]; - let mut rabbit = Rabbit::setup_without_iv(key); - rabbit.encrypt_inplace(&mut d[0..n]); - assert_eq!(&s[..], &d[0..n]); - assert_eq!(rabbit.block_num, (s.len() / MESSAGE_BLOCK_BYTE_LEN) as u64); - - d = [0; 48]; - rabbit.reset(); - rabbit.encrypt_inplace(&mut d[0..n]); - assert_eq!(&s[..], &d[0..n]); - assert_eq!(rabbit.block_num, (s.len() / MESSAGE_BLOCK_BYTE_LEN) as u64); - } - } - }; - ($name:ident $wrap_name:ident $stream_name:ident - key = [$kf:expr, $ke:expr, $kd:expr, $kc:expr, - $kb:expr, $ka:expr, $k9:expr, $k8:expr, - $k7:expr, $k6:expr, $k5:expr, $k4:expr, - $k3:expr, $k2:expr, $k1:expr, $k0:expr] - iv = [$iv7:expr, $iv6:expr, $iv5:expr, $iv4:expr, - $iv3:expr, $iv2:expr, $iv1:expr, $iv0:expr] - S[0] = [$s0f:expr, $s0e:expr, $s0d:expr, $s0c:expr, - $s0b:expr, $s0a:expr, $s09:expr, $s08:expr, - $s07:expr, $s06:expr, $s05:expr, $s04:expr, - $s03:expr, $s02:expr, $s01:expr, $s00:expr] - S[1] = [$s1f:expr, $s1e:expr, $s1d:expr, $s1c:expr, - $s1b:expr, $s1a:expr, $s19:expr, $s18:expr, - $s17:expr, $s16:expr, $s15:expr, $s14:expr, - $s13:expr, $s12:expr, $s11:expr, $s10:expr] - S[2] = [$s2f:expr, $s2e:expr, $s2d:expr, $s2c:expr, - $s2b:expr, $s2a:expr, $s29:expr, $s28:expr, - $s27:expr, $s26:expr, $s25:expr, $s24:expr, - $s23:expr, $s22:expr, $s21:expr, $s20:expr]) => { - #[test] - fn $name() { - let key = [ - $k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7, $k8, $k9, $ka, $kb, $kc, $kd, $ke, $kf, - ]; - let iv = [$iv0, $iv1, $iv2, $iv3, $iv4, $iv5, $iv6, $iv7]; - let s0 = [ - $s00, $s01, $s02, $s03, $s04, $s05, $s06, $s07, $s08, $s09, $s0a, $s0b, $s0c, - $s0d, $s0e, $s0f, - ]; - let s1 = [ - $s10, $s11, $s12, $s13, $s14, $s15, $s16, $s17, $s18, $s19, $s1a, $s1b, $s1c, - $s1d, $s1e, $s1f, - ]; - let s2 = [ - $s20, $s21, $s22, $s23, $s24, $s25, $s26, $s27, $s28, $s29, $s2a, $s2b, $s2c, - $s2d, $s2e, $s2f, - ]; - let mut state = Default::default(); - setup_key(&mut state, key); - setup_iv(&mut state, iv); - next_state(&mut state); - assert_eq!(extract(&state), s0); - next_state(&mut state); - assert_eq!(extract(&state), s1); - next_state(&mut state); - assert_eq!(extract(&state), s2); - } - - #[test] - fn $wrap_name() { - let key = [ - $k0, $k1, $k2, $k3, $k4, $k5, $k6, $k7, $k8, $k9, $ka, $kb, $kc, $kd, $ke, $kf, - ]; - let iv = [$iv0, $iv1, $iv2, $iv3, $iv4, $iv5, $iv6, $iv7]; - let s = [ - $s00, $s01, $s02, $s03, $s04, $s05, $s06, $s07, $s08, $s09, $s0a, $s0b, $s0c, - $s0d, $s0e, $s0f, $s10, $s11, $s12, $s13, $s14, $s15, $s16, $s17, $s18, $s19, - $s1a, $s1b, $s1c, $s1d, $s1e, $s1f, $s20, $s21, $s22, $s23, $s24, $s25, $s26, - $s27, $s28, $s29, $s2a, $s2b, $s2c, $s2d, $s2e, $s2f, - ]; - - let mut d; - - for n in 0..48 { - let s = &s[0..n]; - - d = [0; 48]; - let mut rabbit = Rabbit::setup(key, iv); - rabbit.encrypt_inplace(&mut d[0..n]); - assert_eq!(&s[..], &d[0..n]); - assert_eq!(rabbit.block_num, (s.len() / MESSAGE_BLOCK_BYTE_LEN) as u64); - - d = [0; 48]; - rabbit.reinit(iv); - rabbit.encrypt_inplace(&mut d[0..n]); - assert_eq!(&s[..], &d[0..n]); - assert_eq!(rabbit.block_num, (s.len() / MESSAGE_BLOCK_BYTE_LEN) as u64); - } - } - }; - } +impl BlockSizeUser for RabbitCore { + type BlockSize = BlockSize; +} - // RFC4503 Appendix A. A.1. Testing without IV Setup (page 7) - test_raw! { - without_iv_setup1 - wrapped_without_iv1 - stream_without_iv1 - key = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00] - S[0] = [0xB1,0x57,0x54,0xF0,0x36,0xA5,0xD6,0xEC,0xF5,0x6B,0x45,0x26,0x1C,0x4A,0xF7,0x02] - S[1] = [0x88,0xE8,0xD8,0x15,0xC5,0x9C,0x0C,0x39,0x7B,0x69,0x6C,0x47,0x89,0xC6,0x8A,0xA7] - S[2] = [0xF4,0x16,0xA1,0xC3,0x70,0x0C,0xD4,0x51,0xDA,0x68,0xD1,0x88,0x16,0x73,0xD6,0x96] +impl StreamCipherCore for RabbitCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + // Rabbit can generate 2^64 blocks, but since it does not implement + // the seeking traits, we can assume that so many blocks never will + // be processed + None } - // RFC4503 Appendix A. A.1. Testing without IV Setup (page 7) - test_raw! { - without_iv_setup2 - wrapped_without_iv2 - stream_without_iv2 - key = [0x91,0x28,0x13,0x29,0x2E,0x3D,0x36,0xFE,0x3B,0xFC,0x62,0xF1,0xDC,0x51,0xC3,0xAC] - S[0] = [0x3D,0x2D,0xF3,0xC8,0x3E,0xF6,0x27,0xA1,0xE9,0x7F,0xC3,0x84,0x87,0xE2,0x51,0x9C] - S[1] = [0xF5,0x76,0xCD,0x61,0xF4,0x40,0x5B,0x88,0x96,0xBF,0x53,0xAA,0x85,0x54,0xFC,0x19] - S[2] = [0xE5,0x54,0x74,0x73,0xFB,0xDB,0x43,0x50,0x8A,0xE5,0x3B,0x20,0x20,0x4D,0x4C,0x5E] + fn process_with_backend(&mut self, f: impl StreamClosure) { + f.call(&mut Backend(&mut self.state)); } +} - // RFC4503 Appendix A. A.1. Testing without IV Setup (page 7) - test_raw! { - without_iv_setup3 - wrapped_without_iv3 - stream_without_iv3 - key = [0x83,0x95,0x74,0x15,0x87,0xE0,0xC7,0x33,0xE9,0xE9,0xAB,0x01,0xC0,0x9B,0x00,0x43] - S[0] = [0x0C,0xB1,0x0D,0xCD,0xA0,0x41,0xCD,0xAC,0x32,0xEB,0x5C,0xFD,0x02,0xD0,0x60,0x9B] - S[1] = [0x95,0xFC,0x9F,0xCA,0x0F,0x17,0x01,0x5A,0x7B,0x70,0x92,0x11,0x4C,0xFF,0x3E,0xAD] - S[2] = [0x96,0x49,0xE5,0xDE,0x8B,0xFC,0x7F,0x3F,0x92,0x41,0x47,0xAD,0x3A,0x94,0x74,0x28] - } +struct Backend<'a>(&'a mut State); - // RFC4503 Appendix A. A.2. Testing with IV Setup (page 7) - test_raw! { - with_iv_setup1 - wrapped_with_iv1 - stream_with_iv1 - key = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00] - iv = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00] - S[0] = [0xC6,0xA7,0x27,0x5E,0xF8,0x54,0x95,0xD8,0x7C,0xCD,0x5D,0x37,0x67,0x05,0xB7,0xED] - S[1] = [0x5F,0x29,0xA6,0xAC,0x04,0xF5,0xEF,0xD4,0x7B,0x8F,0x29,0x32,0x70,0xDC,0x4A,0x8D] - S[2] = [0x2A,0xDE,0x82,0x2B,0x29,0xDE,0x6C,0x1E,0xE5,0x2B,0xDB,0x8A,0x47,0xBF,0x8F,0x66] - } +impl<'a> BlockSizeUser for Backend<'a> { + type BlockSize = BlockSize; +} - // RFC4503 Appendix A. A.2. Testing with IV Setup (page 7) - test_raw! { - with_iv_setup2 - wrapped_with_iv2 - stream_with_iv2 - key = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00] - iv = [0xC3,0x73,0xF5,0x75,0xC1,0x26,0x7E,0x59] - S[0] = [0x1F,0xCD,0x4E,0xB9,0x58,0x00,0x12,0xE2,0xE0,0xDC,0xCC,0x92,0x22,0x01,0x7D,0x6D] - S[1] = [0xA7,0x5F,0x4E,0x10,0xD1,0x21,0x25,0x01,0x7B,0x24,0x99,0xFF,0xED,0x93,0x6F,0x2E] - S[2] = [0xEB,0xC1,0x12,0xC3,0x93,0xE7,0x38,0x39,0x23,0x56,0xBD,0xD0,0x12,0x02,0x9B,0xA7] - } +impl<'a> ParBlocksSizeUser for Backend<'a> { + type ParBlocksSize = U1; +} - // RFC4503 Appendix A. A.2. Testing with IV Setup (page 7) - test_raw! { - with_iv_setup3 - wrapped_with_iv3 - stream_with_iv3 - key = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00] - iv = [0xA6,0xEB,0x56,0x1A,0xD2,0xF4,0x17,0x27] - S[0] = [0x44,0x5A,0xD8,0xC8,0x05,0x85,0x8D,0xBF,0x70,0xB6,0xAF,0x23,0xA1,0x51,0x10,0x4D] - S[1] = [0x96,0xC8,0xF2,0x79,0x47,0xF4,0x2C,0x5B,0xAE,0xAE,0x67,0xC6,0xAC,0xC3,0x5B,0x03] - S[2] = [0x9F,0xCB,0xFC,0x89,0x5F,0xA7,0x1C,0x17,0x31,0x3D,0xF0,0x34,0xF0,0x15,0x51,0xCB] +impl<'a> StreamBackend for Backend<'a> { + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + block.copy_from_slice(&self.0.next_block()); } } + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for RabbitCore {} diff --git a/rabbit/tests/mod.rs b/rabbit/tests/mod.rs new file mode 100644 index 00000000..13653bf2 --- /dev/null +++ b/rabbit/tests/mod.rs @@ -0,0 +1,85 @@ +use cipher::{KeyInit, KeyIvInit, StreamCipher}; +use hex_literal::hex; +use rabbit::{Rabbit, RabbitKeyOnly}; + +// RFC4503 Appendix A. A.1. Testing without IV Setup (page 7) +#[test] +fn test_rabbit_key_only() { + let tests = [ + ( + hex!("00000000000000000000000000000000"), + hex!( + "02F74A1C26456BF5ECD6A536F05457B1" + "A78AC689476C697B390C9CC515D8E888" + "96D6731688D168DA51D40C70C3A116F4" + ), + ), + ( + hex!("ACC351DCF162FC3BFE363D2E29132891"), + hex!( + "9C51E28784C37FE9A127F63EC8F32D3D" + "19FC5485AA53BF96885B40F461CD76F5" + "5E4C4D20203BE58A5043DBFB737454E5" + ), + ), + ( + hex!("43009BC001ABE9E933C7E08715749583"), + hex!( + "9B60D002FD5CEB32ACCD41A0CD0DB10C" + "AD3EFF4C1192707B5A01170FCA9FFC95" + "2874943AAD4741923F7FFC8BDEE54996" + ), + ), + ]; + for (key, ks) in tests.iter() { + for n in 1..ks.len() { + let mut rabbit = RabbitKeyOnly::new_from_slice(key).unwrap(); + let mut d = ks.clone(); + for chunk in d.chunks_mut(n) { + rabbit.apply_keystream(chunk); + } + assert!(d.iter().all(|&v| v == 0)); + } + } +} +// RFC4503 Appendix A. A.2. Testing with IV Setup (page 7) +#[test] +fn test_rabbit_key_iv() { + let key = &hex!("00000000000000000000000000000000"); + let tests = [ + ( + hex!("0000000000000000"), + hex!( + "EDB70567375DCD7CD89554F85E27A7C6" + "8D4ADC7032298F7BD4EFF504ACA6295F" + "668FBF478ADB2BE51E6CDE292B82DE2A" + ), + ), + ( + hex!("597E26C175F573C3"), + hex!( + "6D7D012292CCDCE0E2120058B94ECD1F" + "2E6F93EDFF99247B012521D1104E5FA7" + "A79B0212D0BD56233938E793C312C1EB" + ), + ), + ( + hex!("2717F4D21A56EBA6"), + hex!( + "4D1051A123AFB670BF8D8505C8D85A44" + "035BC3ACC667AEAE5B2CF44779F2C896" + "CB5115F034F03D31171CA75F89FCCB9F" + ), + ), + ]; + for (iv, ks) in tests.iter() { + for n in 1..ks.len() { + let mut rabbit = Rabbit::new_from_slices(key, iv).unwrap(); + let mut d = ks.clone(); + for chunk in d.chunks_mut(n) { + rabbit.apply_keystream(chunk); + } + assert!(d.iter().all(|&v| v == 0)); + } + } +} diff --git a/salsa20/CHANGELOG.md b/salsa20/CHANGELOG.md index 33d29003..610ad9ef 100644 --- a/salsa20/CHANGELOG.md +++ b/salsa20/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.10.0 (2022-02-10) +### Changed +- Bump `cipher` dependency to v0.4 ([#276]) + +[#276]: https://github.com/RustCrypto/stream-ciphers/pull/276 + ## 0.9.0 (2021-08-29) ### Removed - `xsalsa` feature: `XSalsa20` is now available by-default ([#271]) diff --git a/salsa20/Cargo.toml b/salsa20/Cargo.toml index 1b96b205..aa0ea1a4 100644 --- a/salsa20/Cargo.toml +++ b/salsa20/Cargo.toml @@ -1,28 +1,28 @@ [package] name = "salsa20" -version = "0.9.0" # Also update html_root_url in lib.rs when bumping this +version = "0.10.0" # Also update html_root_url in lib.rs when bumping this +description = "Salsa20 Stream Cipher" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -description = "Salsa20 Stream Cipher" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/salsa20" repository = "https://github.com/RustCrypto/stream-ciphers" keywords = ["crypto", "stream-cipher", "trait", "xsalsa20"] categories = ["cryptography", "no-std"] -readme = "README.md" -edition = "2018" [dependencies] -cipher = "0.3" - -# optional dependencies -zeroize = { version = ">=1, <1.4", optional = true, default-features = false } +cipher = "0.4" [dev-dependencies] -cipher = { version = "0.3", features = ["dev"] } +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3" [features] -expose-core = [] -hsalsa20 = [] +std = ["cipher/std"] +zeroize = ["cipher/zeroize"] [package.metadata.docs.rs] -features = ["hsalsa20"] +all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/salsa20/README.md b/salsa20/README.md index e50dc623..68e89494 100644 --- a/salsa20/README.md +++ b/salsa20/README.md @@ -37,7 +37,7 @@ USE AT YOUR OWN RISK! ## Minimum Supported Rust Version -Rust **1.41** or higher. +Rust **1.56** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -69,7 +69,7 @@ dual licensed as above, without any additional terms or conditions. [docs-image]: https://docs.rs/salsa20/badge.svg [docs-link]: https://docs.rs/salsa20/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260049-stream-ciphers [hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg diff --git a/salsa20/benches/mod.rs b/salsa20/benches/mod.rs new file mode 100755 index 00000000..f07b023a --- /dev/null +++ b/salsa20/benches/mod.rs @@ -0,0 +1,26 @@ +#![feature(test)] +extern crate test; + +cipher::stream_cipher_bench!( + salsa20::Salsa8; + salsa8_bench1_16b 16; + salsa8_bench2_256b 256; + salsa8_bench3_1kib 1024; + salsa8_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + salsa20::Salsa12; + salsa12_bench1_16b 16; + salsa12_bench2_256b 256; + salsa12_bench3_1kib 1024; + salsa12_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + salsa20::Salsa20; + salsa20_bench1_16b 16; + salsa20_bench2_256b 256; + salsa20_bench3_1kib 1024; + salsa20_bench4_16kib 16384; +); diff --git a/salsa20/benches/salsa20.rs b/salsa20/benches/salsa20.rs deleted file mode 100755 index 14712d30..00000000 --- a/salsa20/benches/salsa20.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![feature(test)] - -cipher::stream_cipher_sync_bench!(salsa20::Salsa20); diff --git a/salsa20/src/core.rs b/salsa20/src/core.rs deleted file mode 100644 index c3501036..00000000 --- a/salsa20/src/core.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! The Salsa20 core function. - -use crate::{rounds::Rounds, Key, Nonce, BLOCK_SIZE, CONSTANTS, STATE_WORDS}; -use core::{convert::TryInto, marker::PhantomData, mem}; - -/// The Salsa20 core function. -// TODO(tarcieri): zeroize support -pub struct Core { - /// Internal state of the core function - state: [u32; STATE_WORDS], - - /// Number of rounds to perform - rounds: PhantomData, -} - -impl Core { - /// Initialize core function with the given key and IV - pub fn new(key: &Key, iv: &Nonce) -> Self { - #[allow(unsafe_code)] - let mut state: [u32; STATE_WORDS] = unsafe { mem::zeroed() }; - state[0] = CONSTANTS[0]; - - for (i, chunk) in key[..16].chunks(4).enumerate() { - state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - state[5] = CONSTANTS[1]; - - for (i, chunk) in iv.chunks(4).enumerate() { - state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - state[8] = 0; - state[9] = 0; - state[10] = CONSTANTS[2]; - - for (i, chunk) in key[16..].chunks(4).enumerate() { - state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - state[15] = CONSTANTS[3]; - - Self { - state, - rounds: PhantomData, - } - } - - /// Generate output, overwriting data already in the buffer - pub fn generate(&mut self, output: &mut [u8]) { - debug_assert_eq!(output.len(), BLOCK_SIZE); - - let mut state = self.state; - self.rounds(&mut state); - - for (i, chunk) in output.chunks_mut(4).enumerate() { - chunk.copy_from_slice(&state[i].to_le_bytes()); - } - } - - /// Apply generated keystream to the output buffer - pub fn apply_keystream(&mut self, counter: u64, output: &mut [u8]) { - debug_assert_eq!(output.len(), BLOCK_SIZE); - self.counter_setup(counter); - - let mut state = self.state; - self.rounds(&mut state); - - for (i, chunk) in output.chunks_mut(4).enumerate() { - for (a, b) in chunk.iter_mut().zip(&state[i].to_le_bytes()) { - *a ^= *b; - } - } - } - - #[inline] - pub(crate) fn counter_setup(&mut self, counter: u64) { - self.state[8] = (counter & 0xffff_ffff) as u32; - self.state[9] = ((counter >> 32) & 0xffff_ffff) as u32; - } - - /// Run the 20 rounds (i.e. 10 double rounds) of Salsa20 - #[inline] - fn rounds(&mut self, state: &mut [u32; STATE_WORDS]) { - for _ in 0..(R::COUNT / 2) { - // column rounds - quarter_round(0, 4, 8, 12, state); - quarter_round(5, 9, 13, 1, state); - quarter_round(10, 14, 2, 6, state); - quarter_round(15, 3, 7, 11, state); - - // diagonal rounds - quarter_round(0, 1, 2, 3, state); - quarter_round(5, 6, 7, 4, state); - quarter_round(10, 11, 8, 9, state); - quarter_round(15, 12, 13, 14, state); - } - - for (s1, s0) in state.iter_mut().zip(&self.state) { - *s1 = s1.wrapping_add(*s0); - } - } -} - -impl From<[u32; STATE_WORDS]> for Core { - fn from(state: [u32; STATE_WORDS]) -> Core { - Self { - state, - rounds: PhantomData, - } - } -} - -#[inline] -#[allow(clippy::many_single_char_names)] -pub(crate) fn quarter_round( - a: usize, - b: usize, - c: usize, - d: usize, - state: &mut [u32; STATE_WORDS], -) { - let mut t: u32; - - t = state[a].wrapping_add(state[d]); - state[b] ^= t.rotate_left(7) as u32; - - t = state[b].wrapping_add(state[a]); - state[c] ^= t.rotate_left(9) as u32; - - t = state[c].wrapping_add(state[b]); - state[d] ^= t.rotate_left(13) as u32; - - t = state[d].wrapping_add(state[c]); - state[a] ^= t.rotate_left(18) as u32; -} diff --git a/salsa20/src/lib.rs b/salsa20/src/lib.rs index 040b82d9..7f8aa406 100644 --- a/salsa20/src/lib.rs +++ b/salsa20/src/lib.rs @@ -1,9 +1,8 @@ -//! The Salsa20 stream cipher. +//! Implementation of the [Salsa] family of stream ciphers. //! -//! Cipher functionality is accessed using traits from re-exported -//! [`cipher`](https://docs.rs/cipher) crate. +//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate. //! -//! # Security Warning +//! # ⚠️ Security Warning: Hazmat! //! //! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity //! is not verified, which can lead to serious vulnerabilities! @@ -15,7 +14,7 @@ //! This diagram illustrates the Salsa quarter round function. //! Each round consists of four quarter-rounds: //! -//! +//! //! //! Legend: //! @@ -23,69 +22,248 @@ //! - ‹‹‹ rotate //! - ⊕ xor //! -//! # Usage -//! +//! # Example //! ``` -//! use salsa20::{Salsa20, Key, Nonce}; -//! use salsa20::cipher::{NewCipher, StreamCipher, StreamCipherSeek}; +//! use salsa20::Salsa20; +//! // Import relevant traits +//! use salsa20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +//! use hex_literal::hex; //! -//! let mut data = [1, 2, 3, 4, 5, 6, 7]; +//! let key = [0x42; 32]; +//! let nonce = [0x24; 8]; +//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F"); +//! let ciphertext = hex!("85843cc5 d58cce7b 5dd3dd04 fa005ded"); //! -//! let key = Key::from_slice(b"an example very very secret key."); -//! let nonce = Nonce::from_slice(b"a nonce."); +//! // Key and IV must be references to the `GenericArray` type. +//! // Here we use the `Into` trait to convert arrays into it. +//! let mut cipher = Salsa20::new(&key.into(), &nonce.into()); //! -//! // create cipher instance -//! let mut cipher = Salsa20::new(&key, &nonce); +//! let mut buffer = plaintext.clone(); //! //! // apply keystream (encrypt) -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [182, 14, 133, 113, 210, 25, 165]); +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, ciphertext); +//! +//! let ciphertext = buffer.clone(); +//! +//! // Salsa ciphers support seeking +//! cipher.seek(0u32); //! -//! // seek to the keystream beginning and apply it again to the `data` (decrypt) -//! cipher.seek(0); -//! cipher.apply_keystream(&mut data); -//! assert_eq!(data, [1, 2, 3, 4, 5, 6, 7]); +//! // decrypt ciphertext by applying keystream again +//! cipher.apply_keystream(&mut buffer); +//! assert_eq!(buffer, plaintext); +//! +//! // stream ciphers can be used with streaming messages +//! cipher.seek(0u32); +//! for chunk in buffer.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buffer, ciphertext); //! ``` +//! +//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20 #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", - html_root_url = "https://docs.rs/salsa20/0.9.0" + html_root_url = "https://docs.rs/salsa20/0.10.0" )] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![deny(unsafe_code)] +#![forbid(unsafe_code)] #![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)] pub use cipher; -mod core; -mod rounds; -mod salsa; +use cipher::{ + consts::{U1, U10, U24, U32, U4, U6, U64, U8}, + generic_array::{typenum::Unsigned, GenericArray}, + Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, ParBlocksSizeUser, StreamBackend, + StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, StreamClosure, +}; +use core::marker::PhantomData; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + mod xsalsa; -pub use crate::{ - salsa::{Key, Nonce, Salsa, Salsa12, Salsa20, Salsa8}, - xsalsa::{XNonce, XSalsa20}, -}; +pub use xsalsa::{hsalsa, XSalsa12, XSalsa20, XSalsa8, XSalsaCore}; -#[cfg(feature = "expose-core")] -pub use crate::{ - core::Core, - rounds::{R12, R20, R8}, -}; +/// Salsa20/8 stream cipher +/// (reduced-round variant of Salsa20 with 8 rounds, *not recommended*) +pub type Salsa8 = StreamCipherCoreWrapper>; + +/// Salsa20/12 stream cipher +/// (reduced-round variant of Salsa20 with 12 rounds, *not recommended*) +pub type Salsa12 = StreamCipherCoreWrapper>; + +/// Salsa20/20 stream cipher +/// (20 rounds; **recommended**) +pub type Salsa20 = StreamCipherCoreWrapper>; -#[cfg(feature = "hsalsa20")] -pub use crate::xsalsa::hsalsa20; +/// Key type used by all Salsa variants and [`XSalsa20`]. +pub type Key = GenericArray; -/// Size of a Salsa20 block in bytes -pub const BLOCK_SIZE: usize = 64; +/// Nonce type used by all Salsa variants. +pub type Nonce = GenericArray; -/// Size of a Salsa20 key in bytes -pub const KEY_SIZE: usize = 32; +/// Nonce type used by [`XSalsa20`]. +pub type XNonce = GenericArray; /// Number of 32-bit words in the Salsa20 state const STATE_WORDS: usize = 16; /// State initialization constant ("expand 32-byte k") const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; + +/// The Salsa20 core function. +pub struct SalsaCore { + /// Internal state of the core function + state: [u32; STATE_WORDS], + /// Number of rounds to perform + rounds: PhantomData, +} + +impl KeySizeUser for SalsaCore { + type KeySize = U32; +} + +impl IvSizeUser for SalsaCore { + type IvSize = U8; +} + +impl BlockSizeUser for SalsaCore { + type BlockSize = U64; +} + +impl KeyIvInit for SalsaCore { + fn new(key: &Key, iv: &Nonce) -> Self { + let mut state = [0u32; STATE_WORDS]; + state[0] = CONSTANTS[0]; + + for (i, chunk) in key[..16].chunks(4).enumerate() { + state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[5] = CONSTANTS[1]; + + for (i, chunk) in iv.chunks(4).enumerate() { + state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[8] = 0; + state[9] = 0; + state[10] = CONSTANTS[2]; + + for (i, chunk) in key[16..].chunks(4).enumerate() { + state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); + } + + state[15] = CONSTANTS[3]; + + Self { + state, + rounds: PhantomData, + } + } +} + +impl StreamCipherCore for SalsaCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + let rem = u64::MAX - self.get_block_pos(); + rem.try_into().ok() + } + fn process_with_backend(&mut self, f: impl StreamClosure) { + f.call(&mut Backend(self)); + } +} + +impl StreamCipherSeekCore for SalsaCore { + type Counter = u64; + + #[inline(always)] + fn get_block_pos(&self) -> u64 { + (self.state[8] as u64) + ((self.state[9] as u64) << 32) + } + + #[inline(always)] + fn set_block_pos(&mut self, pos: u64) { + self.state[8] = (pos & 0xffff_ffff) as u32; + self.state[9] = ((pos >> 32) & 0xffff_ffff) as u32; + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for SalsaCore { + fn drop(&mut self) { + self.state.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for SalsaCore {} + +struct Backend<'a, R: Unsigned>(&'a mut SalsaCore); + +impl<'a, R: Unsigned> BlockSizeUser for Backend<'a, R> { + type BlockSize = U64; +} + +impl<'a, R: Unsigned> ParBlocksSizeUser for Backend<'a, R> { + type ParBlocksSize = U1; +} + +impl<'a, R: Unsigned> StreamBackend for Backend<'a, R> { + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + let res = run_rounds::(&self.0.state); + self.0.set_block_pos(self.0.get_block_pos() + 1); + + for (chunk, val) in block.chunks_exact_mut(4).zip(res.iter()) { + chunk.copy_from_slice(&val.to_le_bytes()); + } + } +} + +#[inline] +#[allow(clippy::many_single_char_names)] +pub(crate) fn quarter_round( + a: usize, + b: usize, + c: usize, + d: usize, + state: &mut [u32; STATE_WORDS], +) { + state[b] ^= state[a].wrapping_add(state[d]).rotate_left(7); + state[c] ^= state[b].wrapping_add(state[a]).rotate_left(9); + state[d] ^= state[c].wrapping_add(state[b]).rotate_left(13); + state[a] ^= state[d].wrapping_add(state[c]).rotate_left(18); +} + +#[inline(always)] +fn run_rounds(state: &[u32; STATE_WORDS]) -> [u32; STATE_WORDS] { + let mut res = *state; + + for _ in 0..R::USIZE { + // column rounds + quarter_round(0, 4, 8, 12, &mut res); + quarter_round(5, 9, 13, 1, &mut res); + quarter_round(10, 14, 2, 6, &mut res); + quarter_round(15, 3, 7, 11, &mut res); + + // diagonal rounds + quarter_round(0, 1, 2, 3, &mut res); + quarter_round(5, 6, 7, 4, &mut res); + quarter_round(10, 11, 8, 9, &mut res); + quarter_round(15, 12, 13, 14, &mut res); + } + + for (s1, s0) in res.iter_mut().zip(state.iter()) { + *s1 = s1.wrapping_add(*s0); + } + res +} diff --git a/salsa20/src/rounds.rs b/salsa20/src/rounds.rs deleted file mode 100644 index 22905591..00000000 --- a/salsa20/src/rounds.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Numbers of rounds allowed to be used with a Salsa20 family stream cipher - -pub trait Rounds: Copy { - const COUNT: usize; -} - -/// 8-rounds (Salsa20/8) -#[derive(Copy, Clone)] -pub struct R8; - -impl Rounds for R8 { - const COUNT: usize = 8; -} - -/// 12-rounds (Salsa20/12) -#[derive(Copy, Clone)] -pub struct R12; - -impl Rounds for R12 { - const COUNT: usize = 12; -} - -/// 20-rounds (Salsa20/20) -#[derive(Copy, Clone)] -pub struct R20; - -impl Rounds for R20 { - const COUNT: usize = 20; -} diff --git a/salsa20/src/salsa.rs b/salsa20/src/salsa.rs deleted file mode 100644 index b0a1a6b3..00000000 --- a/salsa20/src/salsa.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Salsa20 stream cipher implementation. -//! -//! Adapted from the `ctr` crate. - -// TODO(tarcieri): figure out how to unify this with the `ctr` crate (see #95) - -use crate::{ - core::Core, - rounds::{Rounds, R12, R20, R8}, - BLOCK_SIZE, -}; -use cipher::{ - consts::{U32, U8}, - errors::{LoopError, OverflowError}, - NewCipher, SeekNum, StreamCipher, StreamCipherSeek, -}; -use core::fmt; - -#[cfg(docsrs)] -use cipher::generic_array::GenericArray; - -/// Key type. -/// -/// Implemented as an alias for [`GenericArray`]. -/// -/// (NOTE: all three round variants use the same key size) -pub type Key = cipher::CipherKey; - -/// Nonce type. -/// -/// Implemented as an alias for [`GenericArray`]. -pub type Nonce = cipher::Nonce; - -/// Salsa20/8 stream cipher -/// (reduced-round variant of Salsa20 with 8 rounds, *not recommended*) -pub type Salsa8 = Salsa; - -/// Salsa20/12 stream cipher -/// (reduced-round variant of Salsa20 with 12 rounds, *not recommended*) -pub type Salsa12 = Salsa; - -/// Salsa20/20 stream cipher -/// (20 rounds; **recommended**) -pub type Salsa20 = Salsa; - -/// Internal buffer -type Buffer = [u8; BLOCK_SIZE]; - -/// The Salsa20 family of stream ciphers -/// (implemented generically over a number of rounds). -/// -/// We recommend you use the [`Salsa20`] (a.k.a. Salsa20/20) variant. -pub struct Salsa { - /// Salsa core function initialized with a key and IV - block: Core, - - /// Buffer containing previous output - buffer: Buffer, - - /// Position within buffer, or `None` if the buffer is not in use - buffer_pos: u8, - - /// Current counter value relative to the start of the keystream - counter: u64, -} - -impl NewCipher for Salsa { - /// Key size in bytes - type KeySize = U32; - - /// Nonce size in bytes - type NonceSize = U8; - - fn new(key: &Key, nonce: &Nonce) -> Self { - let block = Core::new(key, nonce); - - Self { - block, - buffer: [0u8; BLOCK_SIZE], - buffer_pos: 0, - counter: 0, - } - } -} - -impl StreamCipher for Salsa { - fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { - self.check_data_len(data)?; - let pos = self.buffer_pos as usize; - debug_assert!(BLOCK_SIZE > pos); - - let mut counter = self.counter; - // xor with leftover bytes from the last call if any - if pos != 0 { - if data.len() < BLOCK_SIZE - pos { - let n = pos + data.len(); - xor(data, &self.buffer[pos..n]); - self.buffer_pos = n as u8; - return Ok(()); - } else { - let (l, r) = data.split_at_mut(BLOCK_SIZE - pos); - data = r; - xor(l, &self.buffer[pos..]); - counter += 1; - } - } - - let mut chunks = data.chunks_exact_mut(BLOCK_SIZE); - for chunk in &mut chunks { - self.block.apply_keystream(counter, chunk); - counter += 1; - } - - let rem = chunks.into_remainder(); - self.buffer_pos = rem.len() as u8; - self.counter = counter; - if !rem.is_empty() { - self.block.counter_setup(counter); - self.block.generate(&mut self.buffer); - xor(rem, &self.buffer[..rem.len()]); - } - - Ok(()) - } -} - -impl StreamCipherSeek for Salsa { - fn try_current_pos(&self) -> Result { - T::from_block_byte(self.counter, self.buffer_pos, BLOCK_SIZE as u8) - } - - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - let res = pos.to_block_byte(BLOCK_SIZE as u8)?; - self.counter = res.0; - self.buffer_pos = res.1; - if self.buffer_pos != 0 { - self.block.counter_setup(self.counter); - self.block.generate(&mut self.buffer); - } - Ok(()) - } -} - -impl Salsa { - fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> { - let leftover_bytes = BLOCK_SIZE - self.buffer_pos as usize; - if data.len() < leftover_bytes { - return Ok(()); - } - let blocks = 1 + (data.len() - leftover_bytes) / BLOCK_SIZE; - self.counter - .checked_add(blocks as u64) - .ok_or(LoopError) - .map(|_| ()) - } -} - -impl fmt::Debug for Salsa { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "Cipher {{ .. }}") - } -} - -#[inline(always)] -fn xor(buf: &mut [u8], key: &[u8]) { - debug_assert_eq!(buf.len(), key.len()); - for (a, b) in buf.iter_mut().zip(key) { - *a ^= *b; - } -} diff --git a/salsa20/src/xsalsa.rs b/salsa20/src/xsalsa.rs index 80ce2442..42ce4f34 100644 --- a/salsa20/src/xsalsa.rs +++ b/salsa20/src/xsalsa.rs @@ -1,68 +1,82 @@ //! XSalsa20 is an extended nonce variant of Salsa20 -use crate::{core::quarter_round, Key, Nonce, Salsa20, CONSTANTS}; +use super::{quarter_round, Key, Nonce, SalsaCore, Unsigned, XNonce, CONSTANTS}; use cipher::{ - consts::{U16, U24, U32}, - errors::{LoopError, OverflowError}, + consts::{U10, U16, U24, U32, U4, U6, U64}, generic_array::GenericArray, - NewCipher, SeekNum, StreamCipher, StreamCipherSeek, + BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper, + StreamCipherSeekCore, StreamClosure, }; -use core::convert::TryInto; -/// EXtended Salsa20 nonce (192-bit/24-byte) -pub type XNonce = cipher::Nonce; +#[cfg(feature = "zeroize")] +use cipher::zeroize::ZeroizeOnDrop; /// XSalsa20 is a Salsa20 variant with an extended 192-bit (24-byte) nonce. /// /// Based on the paper "Extending the Salsa20 Nonce": /// /// -/// -/// The `xsalsa20` Cargo feature must be enabled in order to use this -/// (which it is by default). -pub struct XSalsa20(Salsa20); - -impl NewCipher for XSalsa20 { - /// Key size in bytes - type KeySize = U32; +pub type XSalsa20 = StreamCipherCoreWrapper>; +/// XSalsa12 stream cipher (reduced-round variant of [`XSalsa20`] with 12 rounds) +pub type XSalsa12 = StreamCipherCoreWrapper>; +/// XSalsa8 stream cipher (reduced-round variant of [`XSalsa20`] with 8 rounds) +pub type XSalsa8 = StreamCipherCoreWrapper>; - /// Nonce size in bytes - type NonceSize = U24; +/// The XSalsa core function. +pub struct XSalsaCore(SalsaCore); - #[allow(unused_mut, clippy::let_and_return)] - fn new(key: &Key, nonce: &XNonce) -> Self { - let mut subkey = hsalsa20(key, nonce[..16].as_ref().into()); - let mut padded_nonce = Nonce::default(); - padded_nonce.copy_from_slice(&nonce[16..]); +impl KeySizeUser for XSalsaCore { + type KeySize = U32; +} - let mut result = XSalsa20(Salsa20::new(&subkey, &padded_nonce)); +impl IvSizeUser for XSalsaCore { + type IvSize = U24; +} - #[cfg(feature = "zeroize")] - { - use zeroize::Zeroize; - subkey.as_mut_slice().zeroize(); - } +impl BlockSizeUser for XSalsaCore { + type BlockSize = U64; +} - result +impl KeyIvInit for XSalsaCore { + #[inline] + fn new(key: &Key, iv: &XNonce) -> Self { + let subkey = hsalsa::(key, iv[..16].as_ref().into()); + let mut padded_iv = Nonce::default(); + padded_iv.copy_from_slice(&iv[16..]); + XSalsaCore(SalsaCore::new(&subkey, &padded_iv)) } } -impl StreamCipher for XSalsa20 { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - self.0.try_apply_keystream(data) +impl StreamCipherCore for XSalsaCore { + #[inline(always)] + fn remaining_blocks(&self) -> Option { + self.0.remaining_blocks() + } + + #[inline(always)] + fn process_with_backend(&mut self, f: impl StreamClosure) { + self.0.process_with_backend(f); } } -impl StreamCipherSeek for XSalsa20 { - fn try_current_pos(&self) -> Result { - self.0.try_current_pos() +impl StreamCipherSeekCore for XSalsaCore { + type Counter = u64; + + #[inline(always)] + fn get_block_pos(&self) -> u64 { + self.0.get_block_pos() } - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - self.0.try_seek(pos) + #[inline(always)] + fn set_block_pos(&mut self, pos: u64) { + self.0.set_block_pos(pos); } } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for XSalsaCore {} + /// The HSalsa20 function defined in the paper "Extending the Salsa20 nonce" /// /// @@ -74,29 +88,32 @@ impl StreamCipherSeek for XSalsa20 { /// - Nonce (`u32` x 4) /// /// It produces 256-bits of output suitable for use as a Salsa20 key -#[cfg_attr(docsrs, doc(cfg(feature = "hsalsa20")))] -pub fn hsalsa20(key: &Key, input: &GenericArray) -> GenericArray { - let mut state = [0u32; 16]; +pub fn hsalsa(key: &Key, input: &GenericArray) -> GenericArray { + #[inline(always)] + fn to_u32(chunk: &[u8]) -> u32 { + u32::from_le_bytes(chunk.try_into().unwrap()) + } + let mut state = [0u32; 16]; state[0] = CONSTANTS[0]; + state[1..5] + .iter_mut() + .zip(key[0..16].chunks_exact(4)) + .for_each(|(v, chunk)| *v = to_u32(chunk)); state[5] = CONSTANTS[1]; + state[6..10] + .iter_mut() + .zip(input.chunks_exact(4)) + .for_each(|(v, chunk)| *v = to_u32(chunk)); state[10] = CONSTANTS[2]; + state[11..15] + .iter_mut() + .zip(key[16..].chunks_exact(4)) + .for_each(|(v, chunk)| *v = to_u32(chunk)); state[15] = CONSTANTS[3]; - for (i, chunk) in key.chunks(4).take(4).enumerate() { - state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - for (i, chunk) in key.chunks(4).skip(4).enumerate() { - state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - for (i, chunk) in input.chunks(4).enumerate() { - state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - // 20 rounds consisting of 10 column rounds and 10 diagonal rounds - for _ in 0..10 { + for _ in 0..R::USIZE { // column rounds quarter_round(0, 4, 8, 12, &mut state); quarter_round(5, 9, 13, 1, &mut state); @@ -113,7 +130,7 @@ pub fn hsalsa20(key: &Key, input: &GenericArray) -> GenericArray