From d8fdeb908ce44b27ee27e90f65ed564c2a29670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Thu, 10 Feb 2022 14:58:56 +0300 Subject: [PATCH] Migrate block mode crates --- .github/dependabot.yml | 7 + .github/workflows/cbc.yaml | 55 +++++++ .github/workflows/cfb-mode.yaml | 55 +++++++ .github/workflows/cfb8.yaml | 55 +++++++ .github/workflows/ctr.yaml | 55 +++++++ .github/workflows/ige.yaml | 55 +++++++ .github/workflows/ofb.yaml | 55 +++++++ .github/workflows/pcbc.yaml | 55 +++++++ .github/workflows/security-audit.yml | 24 +++ .github/workflows/workspace.yml | 53 +++++++ Cargo.lock | 198 ++++++++++++++++++++++++ Cargo.toml | 10 ++ README.md | 81 ++++++++++ cbc/CHANGELOG.md | 11 ++ cbc/Cargo.toml | 30 ++++ cbc/LICENSE-APACHE | 201 ++++++++++++++++++++++++ cbc/LICENSE-MIT | 26 ++++ cbc/README.md | 60 ++++++++ cbc/benches/aes128.rs | 16 ++ cbc/src/decrypt.rs | 191 +++++++++++++++++++++++ cbc/src/encrypt.rs | 179 ++++++++++++++++++++++ cbc/src/lib.rs | 85 +++++++++++ cbc/tests/aes.rs | 25 +++ cbc/tests/data/aes128.blb | Bin 0 -> 3579 bytes cbc/tests/data/aes192.blb | Bin 0 -> 4429 bytes cbc/tests/data/aes256.blb | Bin 0 -> 4589 bytes cfb-mode/CHANGELOG.md | 58 +++++++ cfb-mode/Cargo.toml | 29 ++++ cfb-mode/LICENSE-APACHE | 201 ++++++++++++++++++++++++ cfb-mode/LICENSE-MIT | 26 ++++ cfb-mode/README.md | 60 ++++++++ cfb-mode/benches/aes128.rs | 16 ++ cfb-mode/src/decrypt.rs | 195 ++++++++++++++++++++++++ cfb-mode/src/encrypt.rs | 181 ++++++++++++++++++++++ cfb-mode/src/lib.rs | 68 +++++++++ cfb-mode/tests/aes.rs | 55 +++++++ cfb-mode/tests/data/aes128.blb | Bin 0 -> 4269 bytes cfb-mode/tests/data/aes192.blb | Bin 0 -> 4429 bytes cfb-mode/tests/data/aes256.blb | Bin 0 -> 4589 bytes cfb8/CHANGELOG.md | 56 +++++++ cfb8/Cargo.toml | 29 ++++ cfb8/LICENSE-APACHE | 201 ++++++++++++++++++++++++ cfb8/LICENSE-MIT | 26 ++++ cfb8/README.md | 60 ++++++++ cfb8/benches/aes128.rs | 16 ++ cfb8/src/decrypt.rs | 183 ++++++++++++++++++++++ cfb8/src/encrypt.rs | 183 ++++++++++++++++++++++ cfb8/src/lib.rs | 68 +++++++++ cfb8/tests/aes.rs | 55 +++++++ cfb8/tests/data/aes128.blb | Bin 0 -> 941 bytes cfb8/tests/data/aes192.blb | Bin 0 -> 1101 bytes cfb8/tests/data/aes256.blb | Bin 0 -> 1261 bytes ctr/CHANGELOG.md | 67 ++++++++ ctr/Cargo.toml | 31 ++++ ctr/LICENSE-APACHE | 201 ++++++++++++++++++++++++ ctr/LICENSE-MIT | 26 ++++ ctr/README.md | 59 ++++++++ ctr/benches/aes128.rs | 26 ++++ ctr/src/backend.rs | 83 ++++++++++ ctr/src/ctr_core.rs | 143 ++++++++++++++++++ ctr/src/flavors.rs | 44 ++++++ ctr/src/flavors/ctr128.rs | 158 +++++++++++++++++++ ctr/src/flavors/ctr32.rs | 158 +++++++++++++++++++ ctr/src/flavors/ctr64.rs | 158 +++++++++++++++++++ ctr/src/lib.rs | 91 +++++++++++ ctr/tests/ctr128/data/aes128-ctr.blb | Bin 0 -> 2039 bytes ctr/tests/ctr128/data/aes256-ctr.blb | Bin 0 -> 2055 bytes ctr/tests/ctr128/mod.rs | 12 ++ ctr/tests/ctr32/be.rs | 86 +++++++++++ ctr/tests/ctr32/le.rs | 96 ++++++++++++ ctr/tests/ctr32/mod.rs | 9 ++ ctr/tests/gost/mod.rs | 55 +++++++ ctr/tests/mod.rs | 5 + ige/CHANGELOG.md | 11 ++ ige/Cargo.toml | 30 ++++ ige/LICENSE-APACHE | 201 ++++++++++++++++++++++++ ige/LICENSE-MIT | 26 ++++ ige/README.md | 60 ++++++++ ige/benches/aes128.rs | 16 ++ ige/src/decrypt.rs | 218 +++++++++++++++++++++++++++ ige/src/encrypt.rs | 218 +++++++++++++++++++++++++++ ige/src/lib.rs | 92 +++++++++++ ige/tests/aes.rs | 12 ++ ige/tests/data/aes128.blb | Bin 0 -> 233 bytes ofb/CHANGELOG.md | 52 +++++++ ofb/Cargo.toml | 29 ++++ ofb/LICENSE-APACHE | 201 ++++++++++++++++++++++++ ofb/LICENSE-MIT | 26 ++++ ofb/README.md | 60 ++++++++ ofb/benches/aes128.rs | 24 +++ ofb/src/backend.rs | 114 ++++++++++++++ ofb/src/lib.rs | 207 +++++++++++++++++++++++++ ofb/tests/aes.rs | 34 +++++ ofb/tests/data/aes128.blb | Bin 0 -> 4269 bytes ofb/tests/data/aes192.blb | Bin 0 -> 4429 bytes ofb/tests/data/aes256.blb | Bin 0 -> 4589 bytes pcbc/CHANGELOG.md | 11 ++ pcbc/Cargo.toml | 30 ++++ pcbc/LICENSE-APACHE | 201 ++++++++++++++++++++++++ pcbc/LICENSE-MIT | 26 ++++ pcbc/README.md | 60 ++++++++ pcbc/benches/aes128.rs | 16 ++ pcbc/src/decrypt.rs | 181 ++++++++++++++++++++++ pcbc/src/encrypt.rs | 181 ++++++++++++++++++++++ pcbc/src/lib.rs | 85 +++++++++++ pcbc/tests/aes.rs | 12 ++ pcbc/tests/data/aes128.blb | Bin 0 -> 167 bytes 107 files changed, 7290 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/cbc.yaml create mode 100644 .github/workflows/cfb-mode.yaml create mode 100644 .github/workflows/cfb8.yaml create mode 100644 .github/workflows/ctr.yaml create mode 100644 .github/workflows/ige.yaml create mode 100644 .github/workflows/ofb.yaml create mode 100644 .github/workflows/pcbc.yaml create mode 100644 .github/workflows/security-audit.yml create mode 100644 .github/workflows/workspace.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 cbc/CHANGELOG.md create mode 100644 cbc/Cargo.toml create mode 100644 cbc/LICENSE-APACHE create mode 100644 cbc/LICENSE-MIT create mode 100644 cbc/README.md create mode 100644 cbc/benches/aes128.rs create mode 100644 cbc/src/decrypt.rs create mode 100644 cbc/src/encrypt.rs create mode 100644 cbc/src/lib.rs create mode 100644 cbc/tests/aes.rs create mode 100644 cbc/tests/data/aes128.blb create mode 100644 cbc/tests/data/aes192.blb create mode 100644 cbc/tests/data/aes256.blb create mode 100644 cfb-mode/CHANGELOG.md create mode 100644 cfb-mode/Cargo.toml create mode 100644 cfb-mode/LICENSE-APACHE create mode 100644 cfb-mode/LICENSE-MIT create mode 100644 cfb-mode/README.md create mode 100644 cfb-mode/benches/aes128.rs create mode 100644 cfb-mode/src/decrypt.rs create mode 100644 cfb-mode/src/encrypt.rs create mode 100644 cfb-mode/src/lib.rs create mode 100644 cfb-mode/tests/aes.rs create mode 100644 cfb-mode/tests/data/aes128.blb create mode 100644 cfb-mode/tests/data/aes192.blb create mode 100644 cfb-mode/tests/data/aes256.blb create mode 100644 cfb8/CHANGELOG.md create mode 100644 cfb8/Cargo.toml create mode 100644 cfb8/LICENSE-APACHE create mode 100644 cfb8/LICENSE-MIT create mode 100644 cfb8/README.md create mode 100644 cfb8/benches/aes128.rs create mode 100644 cfb8/src/decrypt.rs create mode 100644 cfb8/src/encrypt.rs create mode 100644 cfb8/src/lib.rs create mode 100644 cfb8/tests/aes.rs create mode 100644 cfb8/tests/data/aes128.blb create mode 100644 cfb8/tests/data/aes192.blb create mode 100644 cfb8/tests/data/aes256.blb create mode 100644 ctr/CHANGELOG.md create mode 100644 ctr/Cargo.toml create mode 100644 ctr/LICENSE-APACHE create mode 100644 ctr/LICENSE-MIT create mode 100644 ctr/README.md create mode 100644 ctr/benches/aes128.rs create mode 100644 ctr/src/backend.rs create mode 100644 ctr/src/ctr_core.rs create mode 100644 ctr/src/flavors.rs create mode 100644 ctr/src/flavors/ctr128.rs create mode 100644 ctr/src/flavors/ctr32.rs create mode 100644 ctr/src/flavors/ctr64.rs create mode 100644 ctr/src/lib.rs create mode 100644 ctr/tests/ctr128/data/aes128-ctr.blb create mode 100644 ctr/tests/ctr128/data/aes256-ctr.blb create mode 100644 ctr/tests/ctr128/mod.rs create mode 100644 ctr/tests/ctr32/be.rs create mode 100644 ctr/tests/ctr32/le.rs create mode 100644 ctr/tests/ctr32/mod.rs create mode 100644 ctr/tests/gost/mod.rs create mode 100644 ctr/tests/mod.rs create mode 100644 ige/CHANGELOG.md create mode 100644 ige/Cargo.toml create mode 100644 ige/LICENSE-APACHE create mode 100644 ige/LICENSE-MIT create mode 100644 ige/README.md create mode 100644 ige/benches/aes128.rs create mode 100644 ige/src/decrypt.rs create mode 100644 ige/src/encrypt.rs create mode 100644 ige/src/lib.rs create mode 100644 ige/tests/aes.rs create mode 100644 ige/tests/data/aes128.blb create mode 100644 ofb/CHANGELOG.md create mode 100644 ofb/Cargo.toml create mode 100644 ofb/LICENSE-APACHE create mode 100644 ofb/LICENSE-MIT create mode 100644 ofb/README.md create mode 100644 ofb/benches/aes128.rs create mode 100644 ofb/src/backend.rs create mode 100644 ofb/src/lib.rs create mode 100644 ofb/tests/aes.rs create mode 100644 ofb/tests/data/aes128.blb create mode 100644 ofb/tests/data/aes192.blb create mode 100644 ofb/tests/data/aes256.blb create mode 100644 pcbc/CHANGELOG.md create mode 100644 pcbc/Cargo.toml create mode 100644 pcbc/LICENSE-APACHE create mode 100644 pcbc/LICENSE-MIT create mode 100644 pcbc/README.md create mode 100644 pcbc/benches/aes128.rs create mode 100644 pcbc/src/decrypt.rs create mode 100644 pcbc/src/encrypt.rs create mode 100644 pcbc/src/lib.rs create mode 100644 pcbc/tests/aes.rs create mode 100644 pcbc/tests/data/aes128.blb diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5cde165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/cbc.yaml b/.github/workflows/cbc.yaml new file mode 100644 index 0000000..41a3fc4 --- /dev/null +++ b/.github/workflows/cbc.yaml @@ -0,0 +1,55 @@ +name: cbc + +on: + pull_request: + paths: + - "cbc/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: cbc + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/cfb-mode.yaml b/.github/workflows/cfb-mode.yaml new file mode 100644 index 0000000..41880b5 --- /dev/null +++ b/.github/workflows/cfb-mode.yaml @@ -0,0 +1,55 @@ +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.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/cfb8.yaml b/.github/workflows/cfb8.yaml new file mode 100644 index 0000000..53ea01e --- /dev/null +++ b/.github/workflows/cfb8.yaml @@ -0,0 +1,55 @@ +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.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/ctr.yaml b/.github/workflows/ctr.yaml new file mode 100644 index 0000000..4baac54 --- /dev/null +++ b/.github/workflows/ctr.yaml @@ -0,0 +1,55 @@ +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.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/ige.yaml b/.github/workflows/ige.yaml new file mode 100644 index 0000000..6792c36 --- /dev/null +++ b/.github/workflows/ige.yaml @@ -0,0 +1,55 @@ +name: ige + +on: + pull_request: + paths: + - "ige/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: ige + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/ofb.yaml b/.github/workflows/ofb.yaml new file mode 100644 index 0000000..a2b3e0e --- /dev/null +++ b/.github/workflows/ofb.yaml @@ -0,0 +1,55 @@ +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.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/pcbc.yaml b/.github/workflows/pcbc.yaml new file mode 100644 index 0000000..7f26013 --- /dev/null +++ b/.github/workflows/pcbc.yaml @@ -0,0 +1,55 @@ +name: pcbc + +on: + pull_request: + paths: + - "pcbc/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: pcbc + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + profile: minimal + - run: cargo test + - run: cargo test --all-features diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml new file mode 100644 index 0000000..0d82d54 --- /dev/null +++ b/.github/workflows/security-audit.yml @@ -0,0 +1,24 @@ +name: Security Audit +on: + pull_request: + paths: Cargo.lock + push: + branches: master + paths: Cargo.lock + schedule: + - cron: "0 0 * * *" + +jobs: + security_audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Cache cargo bin + uses: actions/cache@v1 + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-cargo-audit-v0.12.0 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml new file mode 100644 index 0000000..599f211 --- /dev/null +++ b/.github/workflows/workspace.yml @@ -0,0 +1,53 @@ +name: Workspace + +on: + pull_request: + paths-ignore: + - README.md + push: + branches: master + paths-ignore: + - README.md + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.56.0 # MSRV + components: clippy + override: true + profile: minimal + - run: cargo clippy --all --all-features -- -D warnings + + rustfmt: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + override: true + profile: minimal + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + benches: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + override: true + - run: cargo build --all --benches diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..53ecea4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,198 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6c3373fb58bb23c6ed0f191f915f0e9459c6929fc430c0d74b8237c521953a" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "blobby" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" + +[[package]] +name = "block-padding" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5808df4b2412175c4db3afb115c83d8d0cd26ca4f30a042026cddef8580e526a" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cbc" +version = "0.1.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "cfb-mode" +version = "0.8.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "cfb8" +version = "0.8.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f3e8c9be82c31c331bc9db0fd70a1068f8a288d980b2414dcaa25ab17ac1e0" +dependencies = [ + "blobby", + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ctr" +version = "0.9.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", + "kuznyechik", + "magma", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "ige" +version = "0.1.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "inout" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d8734d7f28aaff861d726dc3bc8003e2987d2fc26add21f5dab0c35d5c348a" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "kuznyechik" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b8a760d5bebee051e542981fd2a562a6e8265c3262bfec1fd0ee3a0ea37f65" +dependencies = [ + "cipher", +] + +[[package]] +name = "libc" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + +[[package]] +name = "magma" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a003780e63ac83d9e21e3ee2ec69e9ca860b220ea97476bde316ad1a5bb071" +dependencies = [ + "cipher", +] + +[[package]] +name = "ofb" +version = "0.6.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "pcbc" +version = "0.1.0" +dependencies = [ + "aes", + "cipher", + "hex-literal", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zeroize" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e82a512 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +members = [ + "cbc", + "cfb8", + "cfb-mode", + "ctr", + "ige", + "ofb", + "pcbc", +] diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a3e49d --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# RustCrypto: block modes + +[![Project Chat][chat-image]][chat-link] +[![dependency status][deps-image]][deps-link] +![Apache2/MIT licensed][license-image] +[![HAZMAT][hazmat-image]][hazmat-link] + +Collection of [block modes] written in pure Rust generic over +block ciphers. + +## ⚠️ Security Warning: [Hazmat!][hazmat-link] + +Crates in this repository do not ensure ciphertexts are authentic +(i.e. by using a MAC to verify ciphertext integrity), which can lead to +serious vulnerabilities if used incorrectly! + +**USE AT YOUR OWN RISK!** + +## Supported algorithms + +| Name | Crate name | crates.io | Docs | MSRV | +|------|------------|-----------|--------|------| +| [Cipher Block Chaining][CBC] | [`cbc`] | [![crates.io](https://img.shields.io/crates/v/cbc.svg)](https://crates.io/crates/cbc) | [![Documentation](https://docs.rs/cbc/badge.svg)](https://docs.rs/cbc) | ![MSRV 1.56][msrv-1.56] | +| [8-bit Cipher Feedback][CFB-8] | [`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) | ![MSRV 1.56][msrv-1.56] | +| [Full-block Cipher Feedback][CFB] | [`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) | ![MSRV 1.56][msrv-1.56] | +| [Counter][CTR] | [`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) | ![MSRV 1.56][msrv-1.56] | +| [GOST R 34.13-2015] | [`gost-modes`] | [![crates.io](https://img.shields.io/crates/v/gost-modes.svg)](https://crates.io/crates/gost-modes) | [![Documentation](https://docs.rs/gost-modes/badge.svg)](https://docs.rs/gost-modes) | ![MSRV 1.56][msrv-1.56] | +| [Infinite Garble Extension][IGE] | [`ige`] | [![crates.io](https://img.shields.io/crates/v/ige.svg)](https://crates.io/crates/ige) | [![Documentation](https://docs.rs/ige/badge.svg)](https://docs.rs/ige) | ![MSRV 1.56][msrv-1.56] | +| [Output Feedback][OFB] | [`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) | ![MSRV 1.56][msrv-1.56] | +| [Propagating Cipher Block Chaining][PCBC] | [`pcbc`] | [![crates.io](https://img.shields.io/crates/v/pcbc.svg)](https://crates.io/crates/pcbc) | [![Documentation](https://docs.rs/pcbc/badge.svg)](https://docs.rs/pcbc) | ![MSRV 1.56][msrv-1.56] | + +### Minimum Supported Rust Version (MSRV) Policy + +MSRV bumps are considered breaking changes and will be performed only with minor version bump. + +## License + +All crates 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) + +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/308460-block-modes +[deps-image]: https://deps.rs/repo/github/RustCrypto/block-modes/status.svg +[deps-link]: https://deps.rs/repo/github/RustCrypto/block-modes +[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 + +[//]: # (crates) + +[`cbc`]: ./cbc +[`cfb8`]: ./cfb8 +[`cfb-mode`]: ./cfb-mode +[`ctr`]: ./ctr +[`gost-modes`]: ./gost-modes +[`ige`]: ./ige +[`ofb`]: ./ofb +[`pcbc`]: ./pcbc + +[//]: # (links) + +[block modes]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation +[CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC) +[CFB-8]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc. +[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Full-block_CFB +[CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR) +[GOST R 34.13-2015]: https://tc26.ru/standard/gost/GOST_R_3413-2015.pdf +[IGE]: https://www.links.org/files/openssl-ige.pdf +[OFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB) +[PCBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC) diff --git a/cbc/CHANGELOG.md b/cbc/CHANGELOG.md new file mode 100644 index 0000000..b6059ab --- /dev/null +++ b/cbc/CHANGELOG.md @@ -0,0 +1,11 @@ +# 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.1.0 (2022-02-10) +- Initial release ([#2]) + +[#2]: https://github.com/RustCrypto/block-modes/pull/2 diff --git a/cbc/Cargo.toml b/cbc/Cargo.toml new file mode 100644 index 0000000..541af0e --- /dev/null +++ b/cbc/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "cbc" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +description = "Cipher Block Chaining (CBC) block cipher mode of operation" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/cbc" +repository = "https://github.com/RustCrypto/block-modes" +keywords = ["crypto", "block-mode", "ciphers"] +categories = ["cryptography", "no-std"] + +[dependencies] +cipher = "0.4" + +[dev-dependencies] +aes = "0.8" +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3.3" + +[features] +default = ["block-padding"] +block-padding = ["cipher/block-padding"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/cbc/LICENSE-APACHE b/cbc/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/cbc/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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/cbc/LICENSE-MIT b/cbc/LICENSE-MIT new file mode 100644 index 0000000..d19d409 --- /dev/null +++ b/cbc/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2018-2022 RustCrypto Developers +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/cbc/README.md b/cbc/README.md new file mode 100644 index 0000000..f99efea --- /dev/null +++ b/cbc/README.md @@ -0,0 +1,60 @@ +# RustCrypto: CBC + +[![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] + +Generic implementation of the [Cipher Block Chaining][CBC] (CBC) block cipher +mode of operation. + + + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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/cbc.svg +[crate-link]: https://crates.io/crates/cbc +[docs-image]: https://docs.rs/cbc/badge.svg +[docs-link]: https://docs.rs/cbc/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-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/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/cbc/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Acbc+branch%3Amaster + +[//]: # (general links) + +[CBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC +[cipher-doc]: https://docs.rs/cipher/ diff --git a/cbc/benches/aes128.rs b/cbc/benches/aes128.rs new file mode 100644 index 0000000..b478146 --- /dev/null +++ b/cbc/benches/aes128.rs @@ -0,0 +1,16 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::block_encryptor_bench!( + KeyIv: cbc::Encryptor, + cbc_aes128_encrypt_block, + cbc_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: cbc::Decryptor, + cbc_aes128_decrypt_block, + cbc_aes128_decrypt_blocks, +); diff --git a/cbc/src/decrypt.rs b/cbc/src/decrypt.rs new file mode 100644 index 0000000..b3fe6a5 --- /dev/null +++ b/cbc/src/decrypt.rs @@ -0,0 +1,191 @@ +use crate::xor; +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocks, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CBC mode decryptor. +#[derive(Clone)] +pub struct Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockDecryptMut for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.decrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl InnerUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + iv: iv.clone(), + } + } +} + +impl IvState for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cbc::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cbc::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Decryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Decryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = BK::ParBlocksSize; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let enc_block = block.clone_in(); + self.backend.proc_block(block.reborrow()); + xor(block.get_out(), self.iv); + *self.iv = enc_block; + } + + #[inline(always)] + fn proc_par_blocks(&mut self, mut blocks: InOut<'_, '_, ParBlocks>) { + let t = blocks.clone_in(); + self.backend.proc_par_blocks(blocks.reborrow()); + let out = blocks.get_out(); + let n = out.len(); + xor(&mut out[0], self.iv); + for i in 1..n { + xor(&mut out[i], &t[i - 1]) + } + *self.iv = t[n - 1].clone(); + } +} diff --git a/cbc/src/encrypt.rs b/cbc/src/encrypt.rs new file mode 100644 index 0000000..cb19070 --- /dev/null +++ b/cbc/src/encrypt.rs @@ -0,0 +1,179 @@ +use crate::xor; +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CBC mode encryptor. +#[derive(Clone)] +pub struct Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockEncryptMut for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl InnerUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + iv: iv.clone(), + } + } +} + +impl IvState for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cbc::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cbc::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Encryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Encryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut t = block.clone_in(); + xor(&mut t, self.iv); + self.backend.proc_block((&t, block.get_out()).into()); + *self.iv = block.get_out().clone(); + } +} diff --git a/cbc/src/lib.rs b/cbc/src/lib.rs new file mode 100644 index 0000000..4929ad0 --- /dev/null +++ b/cbc/src/lib.rs @@ -0,0 +1,85 @@ +//! [Cipher Block Chaining][1] (CBC) mode. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +//! use hex_literal::hex; +//! +//! type Aes128CbcEnc = cbc::Encryptor; +//! type Aes128CbcDec = cbc::Decryptor; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "c7fe247ef97b21f07cbdd26cb5d346bf" +//! "d27867cb00d9486723e159978fb9a5f9" +//! "14cfb228a710de4171e396e7b6cf859e" +//! ); +//! +//! // encrypt/decrypt in-place +//! // buffer must be big enough for padded plaintext +//! let mut buf = vec![0u8; 48]; +//! let pt_len = plaintext.len(); +//! buf[..pt_len].copy_from_slice(&plaintext[..]); +//! let ct = Aes128CbcEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_mut::(&mut buf, pt_len) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let pt = Aes128CbcDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_mut::(&mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! let mut buf = vec![0u8; 48]; +//! let ct = Aes128CbcEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_b2b_mut::(&plaintext[..], &mut buf) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let mut buf = vec![0u8; 48]; +//! let pt = Aes128CbcDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_b2b_mut::(&ct, &mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/cbc/0.1.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +mod decrypt; +mod encrypt; + +pub use cipher; +pub use decrypt::Decryptor; +pub use encrypt::Encryptor; + +use cipher::generic_array::{ArrayLength, GenericArray}; + +#[inline(always)] +fn xor>(out: &mut GenericArray, buf: &GenericArray) { + for (a, b) in out.iter_mut().zip(buf) { + *a ^= *b; + } +} diff --git a/cbc/tests/aes.rs b/cbc/tests/aes.rs new file mode 100644 index 0000000..5513bf1 --- /dev/null +++ b/cbc/tests/aes.rs @@ -0,0 +1,25 @@ +use aes::*; +use cbc::{Decryptor, Encryptor}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; + +iv_state_test!(aes128_cbc_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes128_cbc_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes192_cbc_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes192_cbc_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes256_cbc_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes256_cbc_dec_iv_state, Decryptor, decrypt); + +// Test vectors from CVAP "AES Multiblock Message Test (MMT) Sample Vectors": +// +block_mode_enc_test!(aes128_cbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128_cbc_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes128enc_cbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128dec_cbc_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes192_cbc_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192_cbc_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes192enc_cbc_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192dec_cbc_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes256_cbc_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256_cbc_dec_test, "aes256", Decryptor); +block_mode_enc_test!(aes256enc_cbc_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256dec_cbc_dec_test, "aes256", Decryptor); diff --git a/cbc/tests/data/aes128.blb b/cbc/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..b74b505a30b4356b3a3482eb3bf76ba51e736690 GIT binary patch literal 3579 zcmV;4VAO`@}1EIwfyExt3%#DA9P-XIwjJZCXr-zyzB!hm4q zo0edkeC~pJCRbYhyL@^wpNMie1uixLr;&74x?v?8AxC4?;e>i>)zv{KqnWD*?lqZU z)vngbVlKo{pz%QcxW5T-*4~qe?}0Cf?b27~%0JqK$IfR#J4I3~K)6Ah+|$ zS8fvB4YodykE|=2IUvXn30;e^pkQgAd>NNY8lZpxn!%Y=&J!84RMR2^VD}szH$NQH zHY5mv77YF5Zolm=c^-{g8WV249mHwgzzji$aw~)!+PtzX8|a}%SK1&BfB+e}V3%^n zKQo31zizcGp$q0vm-w6>2?fL?0sTjL8sf!73oS=67Rq?NpYr=)70=TaQckXY%ayKk zj=rvhq-P+u`LDm_p;jxp6KMQ$teu=7Kb>B_B@XA#I7$VDYXYhlh)$#GLb%9vgn?Ac>_@ za|AQ@=GJ`{({sI3U?BG-RgJSlI#NGQQKi181ebtdfP2L1Xin|V+{!Q*27s{ft|%1q zUpCNF#jd7VS~zSJLU#{Ma+lb-7$xp`{fM7ss@j{wMj6DbC zRmCWJU{DP={5iIcTf3XD*4?rrf`wt$7aA~)TN*`C=%we?a*>TC$!ODu7)a9bW;M#G zgnu|$3mx@}wePuw^2TiX_-|pe`M|HOY?eGp&grfdI>DczzA+R{5c2m4AV+Gi;`` z2Zh<`K$FH94pCBMpNKApO?j+d7JJgCOy#Z!jV|a^_EVk;lzD3&8`R1C+sB&A?@a;j zxR-X(iuW4gr2I$SX5Va0z8x4)P00(yl_s z+j`3HSVYS+uHD9kC57z(Af{=ZEVVcZ&ixjVNX|kjD*CWvL^*gcY*;K9xy}uv(1gxS zJV-44kIx!~2u_Lnh_ng_O?V^ zUnUt_cI)fH?aIUwqWFOz_$%)`bEfzB4YGxDZD}30zg6zST7{iEpx{JbCb_#i7NZs( z(S~kK*z)E0?6l-3`AkF-H%$`S^r=ZHZlsF@b(7+Z^)!B{Xe*@jlZfi@_s{_xT zj@THk-=WWzJCh)1xqWwS&Jc$gZhnzJbV%cZL{Og$uQt7c7vWPj(_9pv)Z`Zji9g$f z0Z)f1fgrGz67%o*?G1&A34^CSt-E|Sc-19=zGSGV{8(>E4<#WfdPj-{m;?^J4^;{J z`w40J+2#Q1?=!KvKJA%kdlr9*r3PB8L!9JqJB=dK2xaC@MemH$3u%NkI)q9Q$jxBe zpy-BcKI07}v-*l;J2Hb);9wB?f^*3IRPPEaX)xtd3a(t$kT08tqcxJsLuDW=6hSvp z!yYFvR|}&y9C&LW9lPr<8LPBNuY{MDBuW|Afj~gnF!`WUG}(b~;slwTKZ2k?Y6MrP z#C6F=ai0A)&tNR z64(CMQu`zEszj9gumLdX-qh!QH^6xWErNhR!zHn$@6lh#LgYXc%0qO&IP=5&KI12I zI}D2M8oba|cP|*+1YkdVEFPwx_F^_4E8wAVyWHv?T|a?9Yv`dU07Y6d1vrI147xGB zEr99_g%(OoTqYXig+YCj@BS{l4GIfU*6J8zJs}MCJPb|S!gdGp=pNY-F_G_v)o(kQ zzHC8SK~O25xJJ%#V6+Qky73pC87EpUgK2|y(u4ZZ)!%-FX$dXU2pA%@WlREpm^3c8WboJQA;UZ#xM2O2&68fb`<8J zApLF&l}h5{wj-E9ir$3j80&p1~^$ovLAsely&rf-MfFi z+Lelp6u{ffuETDNDDn2}?4i!COyPGS(PF{WDrUi8;+`=S$N&zsggIL!Ya23WiBBmF zIhlc<^If1mWMjdG#MkS&WRxEnhN9M$a{Q~PAbT3Q6iWg*FZA;y&wx7bo5*o z|1eFNz*){*-U)yJxoEDjnXPL)jsfy)F51*YQ;c}Oqi)uWr(q+w&CX$30rn9qxC{J0 z7fA(p`yY^+QsH7_dj>7APX6MNq4BR^fB@6@n{nVX2M?Wf7N*NirSDPJ`(xDb{-BA_ zrVdBDv%=vHo1?OV$IE%H{xE3HB7=9r8d&~X4~T=%+tbjNzRDnpH$LX#gO_9tP=fB% z5;8d;8g$plS!XPOOM*u)er-H!fIvm>&<(0g+=6rOcwDZYluCAFqB$NOEJUA2TF$@{ zOS~eKLosKD7eI;^0+D%f^kqX3|G6TY>|t|pTICYcf0L@FtMCc;;|*nIogJ_?Yg^7t zdjR-x0Z&&`bV?X_+*g1=8_T3k-}M9Ae2gx{#O|QzCzW?V%;6=$;TLIyFR6Yd@wk#{ z=GS>6r4pl9F)vG@%`@`R5hx7UJ|6k!&u})?P)OCnb7d;}VI0UCPMxfKPy~E_eOOjI!de;>lVw%mUg0zB_cceLxu#}7uNy0DWCpK2mu!u904vPkG znv_?Xc>E#{a96v*`k!TEfdIOnWM@^{%p#E=QPiQGEcD`WCwj%_cSHpt_RBV4KL;R)7Mhse?y1B+^zmN8G}*uQxHF_=4Do9{*b-BmN4UUJ1u6w6Q(rjA1m+`y)Bnr zak66~bWYZV}6IzW;jmPkz9d5=(v$k*h7o;v2;n+ z?Iyi)5`GZms#nU`QiJFp(RtyegpVVCm_qV8?7kVRc8iQ#Eg50!yiwf`8)x&eW?rPKCNfgqa{*aZq!hN=KFA^H7U{h z4^?G5lq&U|c%o(lVb(fwkQG|R#4{42TO(bpyU1hu@CiW4mLvyl`a}kfd{s=4I2UPC z<#BZ#{0UUl9T7Kiuw-Vj$k9FxOty9HSka~#;ZzB5m^Xpk5z^g-C979Om5pIt@7i%~ B)35*l literal 0 HcmV?d00001 diff --git a/cbc/tests/data/aes192.blb b/cbc/tests/data/aes192.blb new file mode 100644 index 0000000000000000000000000000000000000000..f2fccb124c1a2a0d8ef4b55b00ebc452ee5e6daa GIT binary patch literal 4429 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 + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/cfb-mode/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Acfb-mode+branch%3Amaster + +[//]: # (general links) + +[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB) +[cipher-doc]: https://docs.rs/cipher/ diff --git a/cfb-mode/benches/aes128.rs b/cfb-mode/benches/aes128.rs new file mode 100644 index 0000000..1d5c9df --- /dev/null +++ b/cfb-mode/benches/aes128.rs @@ -0,0 +1,16 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::block_encryptor_bench!( + KeyIv: cfb_mode::Encryptor, + cfb_aes128_encrypt_block, + cfb_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: cfb_mode::Decryptor, + cfb_aes128_decrypt_block, + cfb_aes128_decrypt_blocks, +); diff --git a/cfb-mode/src/decrypt.rs b/cfb-mode/src/decrypt.rs new file mode 100644 index 0000000..b188a89 --- /dev/null +++ b/cfb-mode/src/decrypt.rs @@ -0,0 +1,195 @@ +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, + BlockDecryptMut, BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocks, + ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CFB mode decryptor. +#[derive(Clone)] +pub struct Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockDecryptMut for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl AsyncStreamCipher for Decryptor where C: BlockEncryptMut + BlockCipher {} + +impl InnerUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(mut cipher: C, iv: &Iv) -> Self { + let mut iv = iv.clone(); + cipher.encrypt_block_mut(&mut iv); + Self { cipher, iv } + } +} + +impl IvState for Decryptor +where + C: BlockEncryptMut + BlockDecrypt + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + let mut res = self.iv.clone(); + self.cipher.decrypt_block(&mut res); + res + } +} + +impl AlgorithmName for Decryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Decryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Decryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Decryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = BK::ParBlocksSize; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut t = block.clone_in(); + block.xor_in2out(self.iv); + self.backend.proc_block((&mut t).into()); + *self.iv = t; + } + + #[inline(always)] + fn proc_par_blocks(&mut self, mut blocks: InOut<'_, '_, ParBlocks>) { + let mut t = ParBlocks::::default(); + let b = (blocks.get_in(), &mut t).into(); + self.backend.proc_par_blocks(b); + + let n = t.len(); + blocks.get(0).xor_in2out(self.iv); + for i in 1..n { + blocks.get(i).xor_in2out(&t[i - 1]) + } + *self.iv = t[n - 1].clone(); + } +} diff --git a/cfb-mode/src/encrypt.rs b/cfb-mode/src/encrypt.rs new file mode 100644 index 0000000..42a7fd6 --- /dev/null +++ b/cfb-mode/src/encrypt.rs @@ -0,0 +1,181 @@ +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, + BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CFB mode encryptor. +#[derive(Clone)] +pub struct Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockEncryptMut for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl AsyncStreamCipher for Encryptor where C: BlockEncryptMut + BlockCipher {} + +impl InnerUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(mut cipher: C, iv: &Iv) -> Self { + let mut iv = iv.clone(); + cipher.encrypt_block_mut(&mut iv); + Self { cipher, iv } + } +} + +impl IvState for Encryptor +where + C: BlockEncryptMut + BlockDecrypt + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + let mut res = self.iv.clone(); + self.cipher.decrypt_block(&mut res); + res + } +} + +impl AlgorithmName for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Encryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Encryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + block.xor_in2out(self.iv); + let mut t = block.get_out().clone(); + self.backend.proc_block((&mut t).into()); + *self.iv = t; + } +} diff --git a/cfb-mode/src/lib.rs b/cfb-mode/src/lib.rs new file mode 100644 index 0000000..de7cabc --- /dev/null +++ b/cfb-mode/src/lib.rs @@ -0,0 +1,68 @@ +//! [Cipher feedback][1] (CFB) mode with full block feedback. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{AsyncStreamCipher, KeyIvInit}; +//! use hex_literal::hex; +//! +//! type Aes128CfbEnc = cfb_mode::Encryptor; +//! type Aes128CfbDec = cfb_mode::Decryptor; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "3357121ebb5a29468bd861467596ce3d6f99e251cc2d9f0a598032ae386d0ab995b3" +//! ); +//! +//! // encrypt/decrypt in-place +//! let mut buf = plaintext.to_vec(); +//! Aes128CfbEnc::new(&key.into(), &iv.into()).encrypt(&mut buf); +//! assert_eq!(buf, &ciphertext[..]); +//! +//! Aes128CfbDec::new(&key.into(), &iv.into()).decrypt(&mut buf); +//! assert_eq!(buf, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! // buffer length must be equal to input length +//! let mut buf1 = vec![0u8; 34]; +//! Aes128CfbEnc::new(&key.into(), &iv.into()) +//! .encrypt_b2b(&plaintext[..], &mut buf1) +//! .unwrap(); +//! assert_eq!(buf1, &ciphertext[..]); +//! +//! let mut buf2 = vec![0u8; 34]; +//! Aes128CfbDec::new(&key.into(), &iv.into()) +//! .decrypt_b2b(&buf1, &mut buf2) +//! .unwrap(); +//! assert_eq!(buf2, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB) + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/cfb-mode/0.8.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +mod decrypt; +mod encrypt; + +pub use cipher; +pub use decrypt::Decryptor; +pub use encrypt::Encryptor; diff --git a/cfb-mode/tests/aes.rs b/cfb-mode/tests/aes.rs new file mode 100644 index 0000000..2f8bf98 --- /dev/null +++ b/cfb-mode/tests/aes.rs @@ -0,0 +1,55 @@ +use aes::*; +use cfb_mode::{Decryptor, Encryptor}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; + +iv_state_test!(aes128_cfb_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes128_cfb_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes192_cfb_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes192_cfb_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes256_cfb_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes256_cfb_dec_iv_state, Decryptor, decrypt); + +// Test vectors from CVAP "AES Multiblock Message Test (MMT) Sample Vectors": +// +block_mode_enc_test!(aes128_cfb_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128_cfb_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes128enc_cfb_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128enc_cfb_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes192_cfb_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192_cfb_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes192enc_cfb_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192dec_cfb_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes256_cfb_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256_cfb_dec_test, "aes256", Decryptor); +block_mode_enc_test!(aes256enc_cfb_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256dec_cfb_dec_test, "aes256", Decryptor); + +/// Test methods from the `AsyncStreamCipher` trait. +#[test] +fn aes128_cfb_async_test() { + use cipher::{AsyncStreamCipher, KeyIvInit}; + + type Enc = Encryptor; + type Dec = Decryptor; + + let key = [42; 16]; + let iv = [24; 16]; + let mut pt = [0u8; 101]; + for (i, b) in pt.iter_mut().enumerate() { + *b = (i % 11) as u8; + } + let enc = Enc::new_from_slices(&key, &iv).unwrap(); + let mut ct = pt.clone(); + enc.encrypt(&mut ct); + for i in 1..100 { + let enc = Enc::new_from_slices(&key, &iv).unwrap(); + let mut t = pt.clone(); + let t = &mut t[..i]; + enc.encrypt(t); + assert_eq!(t, &ct[..i]); + + let dec = Dec::new_from_slices(&key, &iv).unwrap(); + dec.decrypt(t); + assert_eq!(t, &pt[..i]); + } +} diff --git a/cfb-mode/tests/data/aes128.blb b/cfb-mode/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..6e2c0c1b21b1724c584b9cf997346ba0c0d3a63c GIT binary patch literal 4269 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 Pp|N5fF&8!0uH-?VWZO2a literal 0 HcmV?d00001 diff --git a/cfb-mode/tests/data/aes192.blb b/cfb-mode/tests/data/aes192.blb new file mode 100644 index 0000000000000000000000000000000000000000..d3267a1fd192c6674149c87726c8a7e05723f23e GIT binary patch literal 4429 zcmV-T5wh+8FdMrt0c|>vh$m??Qtau2G!;WO7_r%Pl_0XTj*Va>3-a0e1D%G{RI8osS^f$s>uk*p~v~vwgJ3C-ebo27=z~{)6l~e(&OMl)!QE6!bA@&H5 zb52wBv4kUYtaj^xjr2?q4kY6f>Lg}h&6#K3V?k#f+!rmeoeCX8FITM3rs*+oZ(noD z_lr!5@U4FN{%RhZSg`)6SQxiQFhqpR35Ogb?p;r%P0OGo#>T$ay7oj!rXZjf*INBS z*%itkv3e69WQ%|RM|2bMn}8L%3K9f9(f+voLFSg+d-iU4c+#Y~OwEp}t6Uz4tjfU| z%`JvLH<*LWM3QxwKgXugYyK3IMoZ7QfB>$UnuoU^_js{&E&0I#%{vD!v`O~FWq9jb zCn!X|1_y&0iRm|)Yh%+i#>^F-Dh6Dh@`a3Mc5IDYM{s0TwTLj0x8|Y+#Tf+Tlfg0Fu<;H`b z{I0HGne5L_{{bD7$X#VO2g8kFk|ANmUL{x=61=urhS>80Ctc^+BiT z)(UtMIaDvy%ZL85c+VSq5MH+8rP#0ko`!s++5e1@6@`C{BKX}lujP_{m{b4b@TP;}M zX6eT6ZGBU-9k4dkm+io^#YRh#?YV9HhO%*9OVS7ZkMBAK&|IVaj%wUwT`oA5I)$U>Z_Nc3am8=^0n zIt+GV&zo2_D*mnGbUy-qW3euboHN@NSn{TA_CN?x^H@W?!a$+HFGVw%zVUT-2*-HE z*}+{vbmOFH#cq?c=-4o5yA3JMXs(P4x9y53UO68-?s5P*RaiJ66UU58*9%!*aRx~( zWkZR=fM7`%AQ!A~$~qg_uw+bpW~Y^>5NBR;cNy2CTuJ864B}+LG^&Un_Y9*-J{k^& zPtsvrqM+tI?us(obDWu=#zsUiq1INvs0NHPtwkDGf- z0O^X{g3i#ABU3Fz$>d`fQkDX;U_Ky?CsjwNUsn^P@O^H>7gg(VwW_}~Gmsy1%l@W7 z#fgVqE0e=2K`#ZUQw5&R=N4C@CYld)d*g^?;SHi zl-Ngh-*h*X_zVW6l?=a=!RA|p-3H`p5Va30YgQ38mI#$SOxuc-+fvqzHq4QWdFCVyI9s@*=97fL_YzNR-g^lu`@vO>~G9;JI^5_*fo;L6v^ zKiXQ5<3%Aavx9iMEW9w?;Br@qMN9ojFir5s@tK@Z@CHwam2y8IJKm<|NbdztyUhu( zn0b0Wfgq376cQMBw$&KjkY8{$tmcgd|JFRY-m??7K@7 z0A2+>!<8ZD2{JYvLOMyLsYaZHzbVMX_|pq@w07h2aOd1*K1QaEa^Y&fpH_h&vgHOj zw+-$xfnO!*g?9n3?LF2$9V8T#`K+jYh!=jT8X#N!5|FXk6r=s6*uX1krb2VCERo0F z(-g1GfQCBWif>s`RJoZY(1>_HZzEfN;He7vUK_m%O2)Cs=KEklG%<$x2A-PnVoLZz z(nf9I5I%wXUSNkM0S@;1`u3eAYI}MEie#Jueuf1VEvYC5Ft7e4r?QXdLVBi}iOYw) z54Mby4P(<8vmlA)68C=fUg&8x*$RwwX2O9$wQ-jMRtt+EOmn%S>Uu+Pdl2Baky6nC zn#32MwA$lJ!@f=mz)eb5RAShErC5ANaLVr0Se+BieL*X`Q8P@jr^gR54J3(7T#$VX zjqcA7&V)R6)dETWb-;ORZ%D%}iWEaW=9w@`jf4&oOG70Rz{fFRvxr;Tsmf@1wjVJE zlrB^H^0FtF4#xFahF@fsY#KFSm^d-2Lgrwr(87F(fk0cyShbY!_%mF07AFIx@CX{g1Uf`MmZgmQJsQZ(boPr6Wq5(2_< zWD^>&H*>V&j;F?ssYdVO;@PN{L)na7-OzOjijxd<8&g}UZlIuw(9dYm9*&seZ3=nQ zrSE0k6MV+VDo&2u8e{3aZifZV^_xpfITl(XfQ-RoCoM4SwO{@}QVGKAo^Aa` zuN#p1kHzuwkVYdQK4)zTm}+wft~~yvV95C(=-QB;0_cxInTHNLuAE{6tim+LYt4jNl;J ztm7uu&vwC3!o1S66Pz&0?&!HaIADxQz6c~1-dSP|Co}ajVbOgcz|AY`%)4w!k_**H zD#upnV7G@Vb|284OkiY`RG^u=g$NvE1Sy;2Va@5 zTwuHbvLyupdaRUPJ&F z$xL_4I`&kq`3XzTlzD7*#n$$1A#&Fv9LX%Ab2uM66&7oP?rpk=5JE3Jwd2m~Li7sa zR&{^?@3a~>pB}G4XHUC-$`t3s-FPPnLgh!bT8qaVw*}}$&^ib3`GP9fk_*{2etIv3 z*VQnL-=JNYI!1;JTf5*DFe@mc(VMC4(Mj6rW+K8DX|a-XHSj9xQ6LP~+|O1}p(40f zL0;xztjT~NR7U^vG6Feh3>l?Sojv_Jw4Y)y2$%LI>kO5Z=nPgkP^r^l#<_HbI=w>X z0*<6NNK(wb{HTG(h+aWDUX8UqPx^9v?mO-ax$YZF+{;R6fFL#&8%<}r&7FdTU11wX zq#Jy@%Vwf-Xo-mp=}fAxHVeVBk_gk8!Ub?fauYUg)TNe5is;*p##d*au!=zc2u{om z4Ex#aoC4s%oW_eHnyoODM41{18omCkf!7Argq5^R+M}ATyDG{c9;t+{Y$*k1*LSe) z>4f_7fIv)4HJe7Lxf>AgF&Gu&Za^1q_*UjI+t-Pd4WX z042EW#wW#(V#)>YLuKt$^V`hMpy= zuowGzFl)*1Xe6~HAw1xPm`suib*;~N{M{?#ha}%^G(0xjn{Fq;8ps!IbyH>M5 zAEz)}(|Qk{I0k=R7G2#=S(AsaS!c@T_70mMdDl%T5Lz&RL`~)OZF`@Pn)vs}9Mi)l_V?l&d zjhQ<(62`=?t|4IuQfwh4Bf$j|#>isi+bxE?$5Eaow?Npa;^f4h4tM}VRX!99DRrAW zmWHIT$9`7=%)s2vn*4ZAQ(O5j$oy5EJ*1AHY_Zs@%uxPu67T~0`1b_&Fzl0w7=p6M z#(nsd$btl3-X^I4im;xbXCT>X4b6nFtL|KB!bfGb zx&WjC^(Wni8{UC_$ymnOd@-jqu>mXyN-*mpGz`yFM;I7uZKtDP2dZq-jK@*5bE_Fd zRG_pxN@B!In=W0(n;kIR$Z#P-vYr(w@;qEOW=2nI;L`iCZbpJ2;DLu}*MB1wl1%s- zHj(0Wfk4Yu0TI6n1M2kjn(`FeXT~K0puPp#S^0 zj8&v7)`8W@I*MVskdRpBEt&cdoc&3YTNMamsH(EV0lBcL6?}t)k= z&|CeW8?%m!>uP~O8X~d3S9R1pX7Q&mgzoR#KGIC<+2X$P@K|RSU$v79U|97&^iVTX zg>R3LnI;}kYtEgQ3OnfMvF@qpK?erU<6ya>9UNj}88w$V5 z83+~%$nv#VoOx6mvsItpxRg0c2QmRt5$Tk!0!kB#q75d0d8Rt!mz%M6S}3yJ84_DmK(1LOOlH{iGw1aH zEbT}PGnh(39e5Vvd**C3!JGTSs33wJ(iT(fZtW2e0hx9=TO`4)IKhhhgdx!U2yXDAjKOp7p89j_z?XRQ-Qi?bq%MxJ0 znDu7rh#fnauUS4+aPl1f>1JqW2{f>@@<&I!uGW|k9f`X+GTlaC&SL%m z>g`&LW1ca2y>%gM_~r{jlWgsL-VT5~l0E$WM!ti1B^l(IeaAuKED)h`KouHof`o0= zV&Ab%aF!!%^`mQx^vTUC0~w=`TDuFfDj?BF6B8gmy`=s%a0`T~K4O3Xy3e(Rz<;6% zjy@xTZUOTV2cifdFL4h40P7RaF3E(Tmpxe49C(Y-uy4-IuHStIaE{R+9o1LI!dsw| zr6W2nfB@@1Fiokd+v)(c0CModui9|p{zbw#eb7I78LcNV@4cL;9m=TTJmFf-{>tXo zfUHN?L7z83+i0lfeBmL}X8S-XgqxA=RTzo)o<=41W9X)M5sdf<4$0)2lg5WCzWW0p zt=^8O4mDuyOOIa++gi?wfFQib)d+?;OM}9|NEn@=i{9c+PjSrX!k8EWxFubvp&g)~ zxEUkwe(ZmrXNK~?(;51$KYAmOy~!OYp;Qsdez`TKQA?jp`?ptb_2{qYKPP}7`E~8` z)yi^$S(sXXzHD{2=Qq{}^YI#vVLs#Rft*hg{gTAB>161gSG)BFBR zH4un7W|yzN@*m)LSjfz1lAC&>DsEUeKspiXG8C=XXTS^7a^(rsyjk;wm^MoHBrwF2 za3zye;~+`L?nl7$@#Ql9^SfKXHj;oqS}<+tjYv`cOi;17)Y|3UT$t@Z0(u7e1{Zty zHcq~_sV%ojun}CN3J-!4SCjG!&qu%}G&k631v1*o^>ZlhoPj+zYg0h}<+#`#4E95@ z8nL1tau1hN)*2E|+Gc3ufIy6Q=V!s9_N~Ekf7C$x0h^NrjtVxQS*@q{MLVAX33FdD zjzlUF2}W+*;4T-d!KZ+mC|F+3`U<-RLu5CH-GtAOiPGNvT{vOf75TKF-t`0{2L+V( z<@OQpi~?DHx<{i*mUhgVSQ$g$aR6_5oh7;>T2Ch%s|9Om80qMlP*9F6ngN&B z7&Osi@Z5{r^S;%9o+SSk7pDTOK;E9lB|&#?KvO%(IMK#iGZ44Dvhz0vw20^Aj^Aqk zksT$u=gdMLz91=8MU5X7VQD+`YL*ys^eTY>ZSb@&Sv%*MQS3p3>sXWsCX9)@mf+;z z!OscZ$OXUaO1d=>g+D`6zSi+4#|*2SN0nuS{+94_*#8edU>g+@R*IIL`gCbSuB+=LlpS1tiFit^?)xDVLF zA=#|&;0*sD>D7ja?`J8`$qDM$6+2tCfgp}oiaCT0;{f`N0totrZ3|^5A!0)+hb31L zrIhxsk+mA@EI+fs47WXry4ja}9yt?qH!8~W5Z^Lq@6htB&tLS@5WKu1ZU*^VFtb`h zp)sRb#LWQiMAeyBs`+Mp()UG|pm<6+?=)77%3bT;-#b2DFcgj=3{JIbKG*#ygvB(# zLplQtc#C_Z@}cotzn_61hI!~T@U-Ce=&;fg612l|t%;5;rX_(hKhhxsTF;iXh$Uw<+}3>DFwhxpelVUh{}8y z8xhfEKm^1|ntBkU8JQwja$%A92zv1tF1<+fJNCg?+~}piuONTIXx3Su&F^$q&AsKh z#kPS!1g2mEmYcqE?%QLJv53B*R!x@QhdGqu%FO~DLG zupYt2-C7DR6Qra)m_L(mMz0-%F3OEAff6wgu|lQi(tgfk09qd;dU6fwvVzQyIrzEtr*2M*J(`? z2J9xU-9?&HyG8_sB%DuQr#(TS*34FziAndZf+e5hqatq$*wlIS3MC^loU_g zCS{25^dcD{?a+n3Aa#}&u)uo|GFAX9Uw#buV<3~8tZWI1FFD($WjH;eNJc=w=pj{3 z7FNGBGSd?s5~^p=DH3c=P8CrYzKG=2WkCBN+bsR7@Fk@?=8(e!YpSutKt5Zj=kyc- z4Vfqq9pTMTjtSc*oO1J3uZ`jdS&S*9al3YJ zJ0L|uH~~PM#X;39UD+ub7+$$j`%hGK{9iTVz$W7tsTEZ^5U*Mwx?!0Y7T}_CQU^lRZ++_~g5el{}Z4;C-T1`ylgSr^HYIWO`TPr0h72s{M^ zd|-I{94|x*(4=lNsbz=>_EClhSy(GD)jGlUCy@_b zDoR)ZXjm&wFKYYKG}Tfywh_M}c;E_bq*G=`rUE29QS=ZQ9czJLZ;+sZZc$62)2+P{ z(fxq)gazEI|@l=_yBu84>q8=pH;sm~5g{O3$+oZijsXrXn zDOf~EQ=5OzTlP@zdr()~@_2UsObGgLpRaZI-~x6(X^gH{AFI%|^_2){5br!oDw~pi zG|@5_KXFYIoR?meLU6B0aZMj2mG&fBZ9xo(Y~w)lYd%FDcD7R0^p)qod}nZF2a(Y5 zr8KZKZ&ViUy*fD{YrVXXk4;fKkB2iPX@cxLfI!I;H1`)f@K`wDc~wYN{SXYDGNMUb zc;@&Z>pxJB$sxSK8ebt56orF_ zun9H$`g%1+d<=Ep-;)d2KoL~cBe<;_o}wOucO$g3OtTt(I8SP0NE|~}Gfo`DMIiKL zSKX-QJT%ZByLtKhWDS5|@9?agc)YD&DkzN$?e*Ko2VY`qZ3n*ub9W=f#q61^_E0!1 z)Y$CLu!1MV< z8ZuznN?w+-dfWW>p^DjwM)LKNW1fu9fM77qUSDp1Ljd1#+a}#)craW}#BY0|_=j21 zP8cc?xSon#(v}dt72_wzPH!1zPZe$2<-caG!rgux+YITZc&?R;06wC|6}zTr1ga1Hrr-fdCxk%?$#jmOr2XbTF4SC!~c} z&Lt9RR*MO8OfFe1)}zO?oXYwGrakpeOyD^xu6_&u1wxEgIuCJj?0{J@h*~v};}>+~ zjN(Yiw+w}C%L$>&u-76lNmM$r3)*hsag78(m_Ql@sY56m<(OZL82v3UoDK1Q7>H#X z=G|u|nBjo{?pAXUjEvygHMwmIKR8|H_J|D1Tgh`tyFe#4g` zqBq{BK?1^M2*V zyu>A-rlh-OO%`zL<4G2^K*)e`9J7@r_vaY}!6M)e=`Vr22!c$Z8a5y_v@82jpQk&! z=%-UNB+xrh-y~SK{fe&eU)%Svi}$w-GOI~jYC=I@_4vOVje@gAnw4MHvaf>KXJ?bd zvJGb@6Kb)rB>o@Qqle*&R|hkofgreTX5uMPRErQVI+G1KSIGj~Hyd=2HV8ENKv+I7 z+6v)XW{x0xqBL2LbLAw~;SqjsV(2F*Tg^z7IIDKPXZ$06YG!&wKjq~lyCl%}X7NOVLC=ckq2jxT=2&vTs%u{$5d z9QD-FeJ<3wJQ0aJdzxgMBFns%sLfbM(KW<98WDw95x6Y>y&{L^#a~6yB-DLWk{H42 z07)nv)j6gN+qeZu+dA{P_|(c0e*Cq~=j_Rj0FNHt|7n2na%Ct6|C5!kdSdvBy03|J z>+c!@*N#ioAxZik;e&xd{@f=B&MqWztBz*Inlw?VLx9>2UYVHsXjC1EnpcK48-7t& zb$xH=M#q|nIIro*GR>l-(4m; z=57x2rdAotbtFFQpg~on$rCv#-XLb)On1l9Z%oOFP2#iwg|0j6g1J&b^hQ7WfwlA) Xx2K%sMq_k(Y + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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.svg +[crate-link]: https://crates.io/crates/cfb +[docs-image]: https://docs.rs/cfb/badge.svg +[docs-link]: https://docs.rs/cfb/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-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/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/cfb/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Acfb+branch%3Amaster + +[//]: # (general links) + +[CFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc. +[cipher-doc]: https://docs.rs/cipher/ diff --git a/cfb8/benches/aes128.rs b/cfb8/benches/aes128.rs new file mode 100644 index 0000000..aa6dd6b --- /dev/null +++ b/cfb8/benches/aes128.rs @@ -0,0 +1,16 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::block_encryptor_bench!( + KeyIv: cfb8::Encryptor, + cfb8_aes128_encrypt_block, + cfb8_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: cfb8::Decryptor, + cfb8_aes128_decrypt_block, + cfb8_aes128_decrypt_blocks, +); diff --git a/cfb8/src/decrypt.rs b/cfb8/src/decrypt.rs new file mode 100644 index 0000000..31362fc --- /dev/null +++ b/cfb8/src/decrypt.rs @@ -0,0 +1,183 @@ +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, + BlockDecryptMut, BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CFB-8 mode decryptor. +#[derive(Clone)] +pub struct Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = U1; +} + +impl BlockDecryptMut for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl AsyncStreamCipher for Decryptor {} + +impl InnerUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Decryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + let iv = iv.clone(); + Self { cipher, iv } + } +} + +impl IvState for Decryptor +where + C: BlockEncryptMut + BlockDecryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Decryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb8::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Decryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb8::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Decryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Decryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = U1; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut t = self.iv.clone(); + self.backend.proc_block((&mut t).into()); + let r = block.get(0).clone_in(); + block.xor_in2out(GenericArray::from_slice(&t[..1])); + let n = self.iv.len(); + for i in 0..n - 1 { + self.iv[i] = self.iv[i + 1]; + } + self.iv[n - 1] = r; + } +} diff --git a/cfb8/src/encrypt.rs b/cfb8/src/encrypt.rs new file mode 100644 index 0000000..54c4c2a --- /dev/null +++ b/cfb8/src/encrypt.rs @@ -0,0 +1,183 @@ +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, + BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// CFB-8 mode encryptor. +#[derive(Clone)] +pub struct Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = U1; +} + +impl BlockEncryptMut for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl AsyncStreamCipher for Encryptor {} + +impl InnerUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + let iv = iv.clone(); + Self { cipher, iv } + } +} + +impl IvState for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb8::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb8::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Encryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Encryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = U1; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut t = self.iv.clone(); + self.backend.proc_block((&mut t).into()); + block.xor_in2out(GenericArray::from_slice(&t[..1])); + let r = block.get_out()[0]; + let n = self.iv.len(); + for i in 0..n - 1 { + self.iv[i] = self.iv[i + 1]; + } + self.iv[n - 1] = r; + } +} diff --git a/cfb8/src/lib.rs b/cfb8/src/lib.rs new file mode 100644 index 0000000..5bc6726 --- /dev/null +++ b/cfb8/src/lib.rs @@ -0,0 +1,68 @@ +//! [Cipher Feedback with eight bit feedback][1] (CFB-8) mode. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{AsyncStreamCipher, KeyIvInit}; +//! use hex_literal::hex; +//! +//! type Aes128Cfb8Enc = cfb8::Encryptor; +//! type Aes128Cfb8Dec = cfb8::Decryptor; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "33b356ce9184290c4c8facc1c0b1f918d5475aeb75b88c161ca65bdf05c7137ff4b0" +//! ); +//! +//! // encrypt/decrypt in-place +//! let mut buf = plaintext.to_vec(); +//! Aes128Cfb8Enc::new(&key.into(), &iv.into()).encrypt(&mut buf); +//! assert_eq!(buf, &ciphertext[..]); +//! +//! Aes128Cfb8Dec::new(&key.into(), &iv.into()).decrypt(&mut buf); +//! assert_eq!(buf, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! // buffer length must be equal to input length +//! let mut buf1 = vec![0u8; 34]; +//! Aes128Cfb8Enc::new(&key.into(), &iv.into()) +//! .encrypt_b2b(&plaintext[..], &mut buf1) +//! .unwrap(); +//! assert_eq!(buf1, &ciphertext[..]); +//! +//! let mut buf2 = vec![0u8; 34]; +//! Aes128Cfb8Dec::new(&key.into(), &iv.into()) +//! .decrypt_b2b(&buf1, &mut buf2) +//! .unwrap(); +//! assert_eq!(buf2, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB-1,_CFB-8,_CFB-64,_CFB-128,_etc. + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/cfb8/0.8.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +mod decrypt; +mod encrypt; + +pub use cipher; +pub use decrypt::Decryptor; +pub use encrypt::Encryptor; diff --git a/cfb8/tests/aes.rs b/cfb8/tests/aes.rs new file mode 100644 index 0000000..f1f7ab9 --- /dev/null +++ b/cfb8/tests/aes.rs @@ -0,0 +1,55 @@ +use aes::*; +use cfb8::{Decryptor, Encryptor}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; + +iv_state_test!(aes128_cfb8_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes128_cfb8_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes192_cfb8_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes192_cfb8_dec_iv_state, Decryptor, decrypt); +iv_state_test!(aes256_cfb8_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes256_cfb8_dec_iv_state, Decryptor, decrypt); + +// Test vectors from CVAP "AES Multiblock Message Test (MMT) Sample Vectors": +// +block_mode_enc_test!(aes128_cfb8_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128_cfb8_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes128enc_cfb8_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128enc_cfb8_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes192_cfb8_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192_cfb8_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes192enc_cfb8_enc_test, "aes192", Encryptor); +block_mode_dec_test!(aes192dec_cfb8_dec_test, "aes192", Decryptor); +block_mode_enc_test!(aes256_cfb8_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256_cfb8_dec_test, "aes256", Decryptor); +block_mode_enc_test!(aes256enc_cfb8_enc_test, "aes256", Encryptor); +block_mode_dec_test!(aes256dec_cfb8_dec_test, "aes256", Decryptor); + +/// Test methods from the `AsyncStreamCipher` trait. +#[test] +fn aes128_cfb8_async_test() { + use cipher::{AsyncStreamCipher, KeyIvInit}; + + type Enc = Encryptor; + type Dec = Decryptor; + + let key = [42; 16]; + let iv = [24; 16]; + let mut pt = [0u8; 101]; + for (i, b) in pt.iter_mut().enumerate() { + *b = (i % 11) as u8; + } + let enc = Enc::new_from_slices(&key, &iv).unwrap(); + let mut ct = pt.clone(); + enc.encrypt(&mut ct); + for i in 1..100 { + let enc = Enc::new_from_slices(&key, &iv).unwrap(); + let mut t = pt.clone(); + let t = &mut t[..i]; + enc.encrypt(t); + assert_eq!(t, &ct[..i]); + + let dec = Dec::new_from_slices(&key, &iv).unwrap(); + dec.decrypt(t); + assert_eq!(t, &pt[..i]); + } +} diff --git a/cfb8/tests/data/aes128.blb b/cfb8/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..81b6cafb9722ddead4ab6ecf0158d50b7e33da9b GIT binary patch literal 941 zcmV;e15*3|AjN%Yor&Lk`|okUfTpnu!yx>!yi5RVhe$bid1@-+#4ZA10wf>}k3GZf z?qJ(xi@ATVM*E{6H3=}0aa_OB6 z&%#IJIy)d{2bg>{ND0fR7-OBy7*R(cRV)x13w#WrL$4)-Q^l%LKoy6K+=9OlhcZwV zFUF<2AkZDDT`VeL+6L-)&$?O6Fd+G(ldX_}Pp>8}I$Ir=SuPS+!J`gH7HyV566vVJ zw|4E*o;@HsZ;@FhKc>#|pmoan&R7>955hKLx7+tPC-BGKG906B6o>EYja8o0Hg_l# zj+{_JRut7h&JiF3?fwWQP`z5n-S$sHpj3Y#Qmok8wk(((ppH?KoWPB%0to^kAUMx= zXHXU{++)MF+v5?dpCFj~y(oxs#19!Jv2M!j6=4MHBm_u!Ajt(hhr{J-#pNs0A7w@P zks!GFWDN-;+7te6xO)Nc&!7g82}uU7w$dQ!XL0rN3W@HA5@FKOjvhuJZiYZUGdX5e zkb2;5@scJl2s?it90-HTvhyI4trP6F3*H;EvrOQ4XJ;!Q`C33=xcCk~gPlw~qdl#Z z3KzM#;cW^@_daDSAnRAS-3M-ayED|k#MkVyejrg3S(l(^+|u9{#c?#n`ce$an4hHR z7!0uL%ks)bAaJY1i?O~*0fX@VJ@sLg{~+Q~@p%_Zaqb>SinHAy1cMG#1=hu;no67mDQ)B?6y8QKy7 znJSoKdBbR4AY+-q+y;xIP?$wxkE!h%mLL&V!gg$iw^ZLqTj||cQQs8)|4uF>SgqeG PR20d=gz2ll=KB%;LFu|1 literal 0 HcmV?d00001 diff --git a/cfb8/tests/data/aes192.blb b/cfb8/tests/data/aes192.blb new file mode 100644 index 0000000000000000000000000000000000000000..f7eb86dd623273a6adc53e6b133155f3c8702d2a GIT binary patch literal 1101 zcmV-T1hV@8FfyUA(;t2K^CT2E@P?Feh<^ zq@+NGnIQEe#MOBGTkX?2?u{`O45RWdGZNn79$cGS27ajg`2u2wexm|^uGD|yes|2HK~?e&sMYhmNy4sFifL&EN5 z4t$ArSSTVOFm8Cv?B~(4qri;t-gArYGt0a8jhpn6EFeq16|w0`dzU2&AvV~6PDT*U z+Coxzargod*)RBzMX`~8FcZm!WprVe^ZM zCcYmnWyKO33S-waWUD-i5>$nReqB5+EW|KVetY&|{Xit3Bw;@lu{MzR(8x!@;fZA+ zI)VAw)4^4+%B@{eD*LobbX>Fy}-phG-Lf}ZN3@NL03~zarqq} zchjY>^)nbtx_OfwCD7PlR~3J_Xk$Z`q^*%Z$UFkQ@>jfzEXnHSBCRM+M*CEZWc@MZlg!17L9Yr*nEycDTPD2+l6A+#gLpeA?{%o%E_@d04y`@{r{J2 z!XR3~#2 zr<86nU>^2oogm^5Iokjw>m*4V&|ngXaR>^n9a9nN3K`6oreZ+sbEjY@EQ9$MakHy? z7>5IH=IAhQtu6z18{Uvvg&t}enSN7yC3noWE1l_t*!#Bt>6{8@D{XL+cOCuZq$I;!|i9FmndyX1KvyTp&)xYK~MN zT>g8CxktaG{Rj{paHXg#t@Y{<9wNTJzO2|CKx7kMXTZuD&hs`6lM4LTCdIHwVWylL zYDm4|+VT%OA0ShAp;XoRNEn}*rF%wf7P1nfY^CZrDl#tb5-|k%XUK{sn&Gc(5Yk(W{VG4?*X`aaSJe&)%aB)Y}LNT z*5Umluf;>S_zgda0+<4CK(@66=(h$_bLuh8RGw1pr^}Z89kEW`th_=1g{w%nM<9m| z%|)>!7z5p}i>(!)CD#Nsas(IDKx)$vUKRUWbSw1!9=Pd@Y#;unQxJTF_Ove1BPt8L zAs`o1Oo_zm<=&?1H9g*Y79$4vK^g|BgdRY3k3XogvWfAZ)t++}zCgJI@Avg(%B)3j z`EoW64X1}7vJ*9~mM7v2uFx`atMU8UL`<)E`?6FnNSIJ*1*-mKspQq%AFov zNxL`s1RsdeS;O(w&L+p0^`6yAlklMC03e#KIJ$!_O9?wrL_l;=eIN{i2kzphs|<8O z49ybSK<))bAt?T*UXh<~Lu|2_@*;$NX^JR6S7+a7Ei=>3cOcTORpc0l2T_D$Q4*pr zz>^NI^QufF<5Ld*1D=V8dqhA8tG&!VxxsOLvHGKp%?OjP)!}X>mUMdckBwpL?fK0h zxh2TlnwwRmH!h~reIdc?5Vtn~P$-M`01)AJx|!-=FWEp=)C8!L`#V2bN~;)?k}$BA zWNJ63fnuM%l5q2h8eHli<&bMO^4WfGEwP(ydOYJ9668shyRiTC5AG7hTAGo1fSPzq zKKF;OW3q27O;fcXShzFbtswjQT2hW6hA + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/ctr/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Actr+branch%3Amaster + +[//]: # (general links) + +[CTR]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR) +[cipher-doc]: https://docs.rs/cipher/ diff --git a/ctr/benches/aes128.rs b/ctr/benches/aes128.rs new file mode 100644 index 0000000..ad30e34 --- /dev/null +++ b/ctr/benches/aes128.rs @@ -0,0 +1,26 @@ +#![feature(test)] +extern crate test; + +cipher::stream_cipher_bench!( + ctr::Ctr32LE; + ctr_32le_aes128_stream_bench1_16b 16; + ctr_32le_aes128_stream_bench2_256b 256; + ctr_32le_aes128_stream_bench3_1kib 1024; + ctr_32le_aes128_stream_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + ctr::Ctr64LE; + ctr_64le_aes128_stream_bench1_16b 16; + ctr_64le_aes128_stream_bench2_256b 256; + ctr_64le_aes128_stream_bench3_1kib 1024; + ctr_64le_aes128_stream_bench4_16kib 16384; +); + +cipher::stream_cipher_bench!( + ctr::Ctr128BE; + ctr_128be_aes128_stream_bench1_16b 16; + ctr_128be_aes128_stream_bench2_256b 256; + ctr_128be_aes128_stream_bench3_1kib 1024; + ctr_128be_aes128_stream_bench4_16kib 16384; +); diff --git a/ctr/src/backend.rs b/ctr/src/backend.rs new file mode 100644 index 0000000..a94fd43 --- /dev/null +++ b/ctr/src/backend.rs @@ -0,0 +1,83 @@ +use crate::CtrFlavor; +use cipher::{ + generic_array::ArrayLength, Block, BlockBackend, BlockClosure, BlockSizeUser, ParBlocks, + ParBlocksSizeUser, StreamBackend, StreamClosure, +}; + +struct Backend<'a, F, B> +where + F: CtrFlavor, + B: BlockBackend, +{ + ctr_nonce: &'a mut F::CtrNonce, + backend: &'a mut B, +} + +impl<'a, F, B> BlockSizeUser for Backend<'a, F, B> +where + F: CtrFlavor, + B: BlockBackend, +{ + type BlockSize = B::BlockSize; +} + +impl<'a, F, B> ParBlocksSizeUser for Backend<'a, F, B> +where + F: CtrFlavor, + B: BlockBackend, +{ + type ParBlocksSize = B::ParBlocksSize; +} + +impl<'a, F, B> StreamBackend for Backend<'a, F, B> +where + F: CtrFlavor, + B: BlockBackend, +{ + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + let tmp = F::next_block(self.ctr_nonce); + self.backend.proc_block((&tmp, block).into()); + } + + #[inline(always)] + fn gen_par_ks_blocks(&mut self, blocks: &mut ParBlocks) { + let mut tmp = ParBlocks::::default(); + for block in tmp.iter_mut() { + *block = F::next_block(self.ctr_nonce); + } + self.backend.proc_par_blocks((&tmp, blocks).into()); + } +} + +pub(crate) struct Closure<'a, F, BS, SC> +where + F: CtrFlavor, + BS: ArrayLength, + SC: StreamClosure, +{ + pub(crate) ctr_nonce: &'a mut F::CtrNonce, + pub(crate) f: SC, +} + +impl<'a, F, BS, SC> BlockSizeUser for Closure<'a, F, BS, SC> +where + F: CtrFlavor, + BS: ArrayLength, + SC: StreamClosure, +{ + type BlockSize = BS; +} + +impl<'a, F, BS, SC> BlockClosure for Closure<'a, F, BS, SC> +where + F: CtrFlavor, + BS: ArrayLength, + SC: StreamClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { ctr_nonce, f } = self; + f.call(&mut Backend:: { ctr_nonce, backend }) + } +} diff --git a/ctr/src/ctr_core.rs b/ctr/src/ctr_core.rs new file mode 100644 index 0000000..7868f5b --- /dev/null +++ b/ctr/src/ctr_core.rs @@ -0,0 +1,143 @@ +use crate::{backend::Closure, CtrFlavor}; +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + AlgorithmName, BlockCipher, BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, + StreamCipherCore, StreamCipherSeekCore, StreamClosure, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::ZeroizeOnDrop; + +/// Generic CTR block mode isntance. +#[derive(Clone)] +pub struct CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + cipher: C, + ctr_nonce: F::CtrNonce, +} + +impl BlockSizeUser for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + type BlockSize = C::BlockSize; +} + +impl StreamCipherCore for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + #[inline] + fn remaining_blocks(&self) -> Option { + F::remaining(&self.ctr_nonce) + } + + #[inline] + fn process_with_backend(&mut self, f: impl StreamClosure) { + let Self { cipher, ctr_nonce } = self; + cipher.encrypt_with_backend_mut(Closure:: { ctr_nonce, f }); + } +} + +impl StreamCipherSeekCore for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + type Counter = F::Backend; + + #[inline] + fn get_block_pos(&self) -> Self::Counter { + F::as_backend(&self.ctr_nonce) + } + + #[inline] + fn set_block_pos(&mut self, pos: Self::Counter) { + F::set_from_backend(&mut self.ctr_nonce, pos); + } +} + +impl InnerUser for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + type Inner = C; +} + +impl IvSizeUser for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + ctr_nonce: F::from_nonce(iv), + } + } +} + +impl IvState for CtrCore +where + C: BlockEncryptMut + BlockCipher, + F: CtrFlavor, +{ + #[inline] + fn iv_state(&self) -> Iv { + F::current_block(&self.ctr_nonce) + } +} + +impl AlgorithmName for CtrCore +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, + F: CtrFlavor, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Ctr")?; + f.write_str(F::NAME)?; + f.write_str("<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for CtrCore +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, + F: CtrFlavor, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Ctr")?; + f.write_str(F::NAME)?; + f.write_str("<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for CtrCore +where + C: BlockEncryptMut + BlockCipher + ZeroizeOnDrop, + F: CtrFlavor, + F::CtrNonce: ZeroizeOnDrop, +{ +} diff --git a/ctr/src/flavors.rs b/ctr/src/flavors.rs new file mode 100644 index 0000000..30361c8 --- /dev/null +++ b/ctr/src/flavors.rs @@ -0,0 +1,44 @@ +//! CTR mode flavors + +use cipher::{ + generic_array::{ArrayLength, GenericArray}, + Counter, +}; + +mod ctr128; +mod ctr32; +mod ctr64; + +pub use ctr128::{Ctr128BE, Ctr128LE}; +pub use ctr32::{Ctr32BE, Ctr32LE}; +pub use ctr64::{Ctr64BE, Ctr64LE}; + +/// Trait implemented by different CTR flavors. +pub trait CtrFlavor> { + /// Inner representation of nonce. + type CtrNonce: Clone; + /// Backend numeric type + type Backend: Counter; + /// Flavor name + const NAME: &'static str; + + /// Return number of remaining blocks. + /// + /// If result does not fit into `usize`, returns `None`. + fn remaining(cn: &Self::CtrNonce) -> Option; + + /// Generate block for given `nonce` and current counter value. + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray; + + /// Generate block for given `nonce` and current counter value. + fn current_block(cn: &Self::CtrNonce) -> GenericArray; + + /// Initialize from bytes. + fn from_nonce(block: &GenericArray) -> Self::CtrNonce; + + /// Convert from a backend value + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend); + + /// Convert to a backend value + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend; +} diff --git a/ctr/src/flavors/ctr128.rs b/ctr/src/flavors/ctr128.rs new file mode 100644 index 0000000..9d10385 --- /dev/null +++ b/ctr/src/flavors/ctr128.rs @@ -0,0 +1,158 @@ +//! 128-bit counter falvors. +use super::CtrFlavor; +use cipher::{ + generic_array::{ArrayLength, GenericArray}, + typenum::{PartialDiv, PartialQuot, Unsigned, U16}, +}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +type ChunkSize = U16; +type Chunks = PartialQuot; +const CS: usize = ChunkSize::USIZE; + +#[derive(Clone)] +pub struct CtrNonce128> { + ctr: u128, + nonce: GenericArray, +} + +#[cfg(feature = "zeroize")] +impl> Drop for CtrNonce128 { + fn drop(&mut self) { + self.ctr.zeroize(); + self.nonce.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl> ZeroizeOnDrop for CtrNonce128 {} + +/// 128-bit big endian counter flavor. +pub enum Ctr128BE {} + +impl CtrFlavor for Ctr128BE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce128>; + type Backend = u128; + const NAME: &'static str = "128BE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u128::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == Chunks::::USIZE - 1 { + cn.ctr.wrapping_add(cn.nonce[i]).to_be_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == Chunks::::USIZE - 1 { + u128::from_be_bytes(chunk) + } else { + u128::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} + +/// 128-bit big endian counter flavor. +pub enum Ctr128LE {} + +impl CtrFlavor for Ctr128LE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce128>; + type Backend = u128; + const NAME: &'static str = "128LE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u128::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == 0 { + cn.ctr.wrapping_add(cn.nonce[i]).to_le_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == 0 { + u128::from_le_bytes(chunk) + } else { + u128::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} diff --git a/ctr/src/flavors/ctr32.rs b/ctr/src/flavors/ctr32.rs new file mode 100644 index 0000000..17cb011 --- /dev/null +++ b/ctr/src/flavors/ctr32.rs @@ -0,0 +1,158 @@ +//! 32-bit counter falvors. +use super::CtrFlavor; +use cipher::{ + generic_array::{ArrayLength, GenericArray}, + typenum::{PartialDiv, PartialQuot, Unsigned, U4}, +}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +type ChunkSize = U4; +type Chunks = PartialQuot; +const CS: usize = ChunkSize::USIZE; + +#[derive(Clone)] +pub struct CtrNonce32> { + ctr: u32, + nonce: GenericArray, +} + +#[cfg(feature = "zeroize")] +impl> Drop for CtrNonce32 { + fn drop(&mut self) { + self.ctr.zeroize(); + self.nonce.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl> ZeroizeOnDrop for CtrNonce32 {} + +/// 32-bit big endian counter flavor. +pub enum Ctr32BE {} + +impl CtrFlavor for Ctr32BE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce32>; + type Backend = u32; + const NAME: &'static str = "32BE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u32::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == Chunks::::USIZE - 1 { + cn.ctr.wrapping_add(cn.nonce[i]).to_be_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == Chunks::::USIZE - 1 { + u32::from_be_bytes(chunk) + } else { + u32::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} + +/// 32-bit big endian counter flavor. +pub enum Ctr32LE {} + +impl CtrFlavor for Ctr32LE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce32>; + type Backend = u32; + const NAME: &'static str = "32LE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u32::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == 0 { + cn.ctr.wrapping_add(cn.nonce[i]).to_le_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == 0 { + u32::from_le_bytes(chunk) + } else { + u32::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} diff --git a/ctr/src/flavors/ctr64.rs b/ctr/src/flavors/ctr64.rs new file mode 100644 index 0000000..3e63abe --- /dev/null +++ b/ctr/src/flavors/ctr64.rs @@ -0,0 +1,158 @@ +//! 64-bit counter falvors. +use super::CtrFlavor; +use cipher::{ + generic_array::{ArrayLength, GenericArray}, + typenum::{PartialDiv, PartialQuot, Unsigned, U8}, +}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +type ChunkSize = U8; +type Chunks = PartialQuot; +const CS: usize = ChunkSize::USIZE; + +#[derive(Clone)] +pub struct CtrNonce64> { + ctr: u64, + nonce: GenericArray, +} + +#[cfg(feature = "zeroize")] +impl> Drop for CtrNonce64 { + fn drop(&mut self) { + self.ctr.zeroize(); + self.nonce.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl> ZeroizeOnDrop for CtrNonce64 {} + +/// 64-bit big endian counter flavor. +pub enum Ctr64BE {} + +impl CtrFlavor for Ctr64BE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce64>; + type Backend = u64; + const NAME: &'static str = "64BE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u64::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == Chunks::::USIZE - 1 { + cn.ctr.wrapping_add(cn.nonce[i]).to_be_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == Chunks::::USIZE - 1 { + u64::from_be_bytes(chunk) + } else { + u64::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} + +/// 64-bit big endian counter flavor. +pub enum Ctr64LE {} + +impl CtrFlavor for Ctr64LE +where + B: ArrayLength + PartialDiv, + Chunks: ArrayLength, +{ + type CtrNonce = CtrNonce64>; + type Backend = u64; + const NAME: &'static str = "64LE"; + + #[inline] + fn remaining(cn: &Self::CtrNonce) -> Option { + (core::u64::MAX - cn.ctr).try_into().ok() + } + + #[inline(always)] + fn current_block(cn: &Self::CtrNonce) -> GenericArray { + let mut block = GenericArray::::default(); + for i in 0..Chunks::::USIZE { + let t = if i == 0 { + cn.ctr.wrapping_add(cn.nonce[i]).to_le_bytes() + } else { + cn.nonce[i].to_ne_bytes() + }; + block[CS * i..][..CS].copy_from_slice(&t); + } + block + } + + #[inline] + fn next_block(cn: &mut Self::CtrNonce) -> GenericArray { + let block = Self::current_block(cn); + cn.ctr = cn.ctr.wrapping_add(1); + block + } + + #[inline] + fn from_nonce(block: &GenericArray) -> Self::CtrNonce { + let mut nonce = GenericArray::>::default(); + for i in 0..Chunks::::USIZE { + let chunk = block[CS * i..][..CS].try_into().unwrap(); + nonce[i] = if i == 0 { + u64::from_le_bytes(chunk) + } else { + u64::from_ne_bytes(chunk) + } + } + let ctr = 0; + Self::CtrNonce { ctr, nonce } + } + + #[inline] + fn as_backend(cn: &Self::CtrNonce) -> Self::Backend { + cn.ctr + } + + #[inline] + fn set_from_backend(cn: &mut Self::CtrNonce, v: Self::Backend) { + cn.ctr = v; + } +} diff --git a/ctr/src/lib.rs b/ctr/src/lib.rs new file mode 100644 index 0000000..bafb064 --- /dev/null +++ b/ctr/src/lib.rs @@ -0,0 +1,91 @@ +//! Generic implementations of [CTR mode][1] for block ciphers. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; +//! use hex_literal::hex; +//! +//! type Aes128Ctr64LE = ctr::Ctr64LE; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "3357121ebb5a29468bd861467596ce3da59bdee42dcc0614dea955368d8a5dc0cad4" +//! ); +//! +//! // encrypt in-place +//! let mut buf = plaintext.to_vec(); +//! let mut cipher = Aes128Ctr64LE::new(&key.into(), &iv.into()); +//! cipher.apply_keystream(&mut buf); +//! assert_eq!(buf, &ciphertext[..]); +//! +//! // CTR mode can be used with streaming messages +//! let mut cipher = Aes128Ctr64LE::new(&key.into(), &iv.into()); +//! for chunk in buf.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buf, &plaintext[..]); +//! +//! // CTR mode supports seeking +//! cipher.seek(0u32); +//! +//! // encrypt/decrypt from buffer to buffer +//! // buffer length must be equal to input length +//! let mut buf1 = vec![0u8; 34]; +//! cipher +//! .apply_keystream_b2b(&plaintext[..], &mut buf1) +//! .unwrap(); +//! assert_eq!(buf1, &ciphertext[..]); +//! +//! let mut buf2 = vec![0u8; 34]; +//! cipher.seek(0u32); +//! cipher.apply_keystream_b2b(&buf1, &mut buf2).unwrap(); +//! assert_eq!(buf2, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CTR + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/ctr/0.9.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +pub mod flavors; + +mod backend; +mod ctr_core; + +pub use cipher; +pub use flavors::CtrFlavor; + +use cipher::StreamCipherCoreWrapper; +pub use ctr_core::CtrCore; + +/// CTR mode with 128-bit big endian counter. +pub type Ctr128BE = StreamCipherCoreWrapper>; +/// CTR mode with 128-bit little endian counter. +pub type Ctr128LE = StreamCipherCoreWrapper>; +/// CTR mode with 64-bit big endian counter. +pub type Ctr64BE = StreamCipherCoreWrapper>; +/// CTR mode with 64-bit little endian counter. +pub type Ctr64LE = StreamCipherCoreWrapper>; +/// CTR mode with 32-bit big endian counter. +pub type Ctr32BE = StreamCipherCoreWrapper>; +/// CTR mode with 32-bit little endian counter. +pub type Ctr32LE = StreamCipherCoreWrapper>; diff --git a/ctr/tests/ctr128/data/aes128-ctr.blb b/ctr/tests/ctr128/data/aes128-ctr.blb new file mode 100644 index 0000000000000000000000000000000000000000..d721e4ec446b1b483a24b8d09cf2f5fbab81ce22 GIT binary patch literal 2039 zcmVH?%aNi8yBGE*I`MX`=^z^J|NF@kJUiIX=f(q8%1gUF{hWzy^!-5oQY^tuW<U?m#sZovnrI(**apt@qOP*f4v(mR;ceUOHo>@f7=FA3RgxlzRub}+@p2SE z0uenRz+gp=VKPP zvdnETAeRe(uJ=O*0=z=k)U}c9nP|@p*zhK%NuM2u z7EpLz6#s!+94J1}_>mn8-({Ms`vs9!UkWn7E$2mzP#m`txEX-4+AiN%8UnZ^0Qc!< zMNvmTX^74|oMbb1 zN;2LiQ3u`lso$7dK*|0T6`eqNM0T<-zmL}>&^Q$pcGCNl$63Om01TYPFq&}{VY2~U zoc2D^l7OlxF9+BfcB{bo71!uf_|wzZOK}=8yF}^I8@WfjXcN4NOD-<|n8c=aIw0#L zs4Mh7>BE@PU?)-PM9{7B6oTM79ENRR50qTl4*T$&S;VyVTxin>9w~DHY^I~Be0t>* za0$Jm1DJQalt6y#0E4i?`n*yt{ECVs0y*^Ea%Dk=t4)5N-+kHa2vJ9F{pGTN9CL^Q zr{yjrus&>UH!_ME6_=-Tv>)MD>pa=X)(&72g}Nfn9zQ<-%>jZvqiH4L?6(ElSj%uOc|?^gTWK#4cV}!9N#uO*(+z zDYjqEyPO;lV`*#~x^;^`VWuTtn0R>wJ~92RJwk=9fQ`&%)3JiU&f6yXbY|!!6kjd= zBVa&XIR_ln_$Hl@QDpk2fp~=Lg<F8Ov=5OD_Y$SJTicl=>+Uu?%$;$UIR709UZ0X)N+!th^a+R@30cThRdK=jTxE6j zx|IeDuZA$*(s&miYRP}^mOvfs_Davc(=tV4@!gt%@b(DRSwgW`rn8ED=Ci6_f9F|) z678#Iyh(wGU{2VoXq7(Q|6yr&fVZ__EP9J=bLfk_$$-GYIvvZgi-*{#QaBa!=lsc+ zDPC&T;o!q0{G$c0Z>KRH1;>ilI2o?LSLr_?`Rl{iozGqq0``I7ZVTIyCjTJ}7%$n$ zYbcO*riRuX8e}>nzly%X@iigB5z=1k)&9-lV^EXZwoR!AGWPptgPrmVmSw@3?dGPJ z-9ySuW_-O0Qq4%HkbVz6ZblP$rKHZGP8}orD`fc$n%TUj4zLeD0gI)owLXxfSZH6} zwoi3`BInXV_BL3ifM;;GFm>rNZ@+|Yt=YTEQ$=)tSjqgbhs@&}XsQ~QD8;S*~OoV3rygG{dU{aG^Wa4`2%$GKz$Iu6cz z9q?DNOb7L*f%%Z;s7jv6e9NTvrB6<&1od^Au9w~cyQpWnM_WhM=z}^ z16Pg0_r&YPS%5;q*hzA{P;vEFK!bH(0T)~>k{LM$x3c+Lj?iHp;>kaX_q zfT~L-@$g;{@C!gqvX8m^IaIylJJjSZhCGRA{5oW!ap{C+>6(8%e(KkqxnyjevE=7< VYVN$AuMsq5Y*Li)97ec1(!p2h`XB%R literal 0 HcmV?d00001 diff --git a/ctr/tests/ctr128/data/aes256-ctr.blb b/ctr/tests/ctr128/data/aes256-ctr.blb new file mode 100644 index 0000000000000000000000000000000000000000..47daaf84de9a0c16a03f2ad5c386108d2f241b62 GIT binary patch literal 2055 zcmV+i2>AB^K!E|&noS)YQo+U;QkV3>&EDlaF@Zr9!lb7W61EdBf*_5O)*U?$3!~rD z@+OvSk--Ys_KU}Z@{#X$a2V%NZ8IrjqwW&=HOc`LFHiEx@4+e?}MBA0+YFA zKqA)c5fRVn!(EDcZ(i|4#LuChX82Fubg`$8i`UjI9iA?X4;RKu^d3$!0PLXdq8d!22;*tgN2RyRC z{)dnXr_W%Q`;G6~6LRn)MzdhUq4J$MIxoaqO&t4TS_DO{7`~v$$*nk_fe^^B#C_LH z#-wobS>Jjk$FOsLi?N6#QoP4R=k9#8Hxdx9v7C`V08;8W$(OBNwfSJDbf&ng;wR=@ z2Kf7yBdlY2EovSk+AT8cG0kjCK>r4;vu zbwoRJCxI;8~y40+ZAq|Z~lr*)qLLjUy4GybsI2touX({J*NcnZzDd# z9}E90$o5;gKU&fnBP~Mf7Tczul4EUU7s2c3w8vHMA_3$t#C9YzTE?YWY(&wKJ1F;% z>0c}EH&fc>)7H_IOk#^ndHjkqTgqgZq^lj7fY#J26rQ`m*Iwrn$K>o1r;#WBW zg$?oiDqRBt5kkR(f3pM05Xbr6skkiN8snYLo+D!+Ez4gESWd~Y&9P`>KU;4U>uShd zG9#ZuijqN;tcE}@-ZHOk-gy}P_5ny#sBJ0Hp{EsHhmy=1M^ozRno1J8bT`NEy{xp~ zB4NLv%RfI5sE#-LM+MZn;<5Lpj!?2T5@kjYzY-?W=yWhKE|3Ik(bL{J=xsCG0TdqU zKBQIs1jp}GtoboN-3QnfkpI*x`euPFvC_)AaS@Cq+!gEUOY|e&Rq5U97#Tb4`9@p~ zN6C>#cEx}Yt_E~kT~`^9+P*tS&;npOIKd?|4NofQneHhXrrH#qqFT9I#Aa!Xf!{ic zX}hB0nthLBf`qbCzn{_UCC~Uy!M$MDpOb zRnl>jS#I1H2VrFGMpM$>yU|XcCwkC7-Eq-dgu+rd*UMk+wath-vxh24!3)x7!@6{p z2vis=i{^|w;GMwBR9&+Gt4@J;GP{8 z;p6a`eALC*tu{g>xT4SOXJ(mLp*Y*Jbdg9)y~JMj`_CmNMq{!j#n8tk}Kd@55}f=RipoI53OdD52-(OS}Eq!%pnU zfwAs|^eMJmFa2KLgOY*FTiv;NcFV{rA4Y`+Iksp~9Wurivh_toMTk_4LZ{v04Z2GF z%nO>;nAeu`%lmiN*eAt%p;)4*$|X!`s|oxqt7!SLRffE#luZZ+H)UjL`dB&6BMf@u zQ#Rv6Cay!h=h}_W^BiNVV?B36q}`}=sU4l|Z61-J4M6;0+a-~&IEa^uF)WW6&2;;i zWj0Qa*cN-k6g^8zmC$z1E!_3gZ3=~~M|Ls$v3bYpLhQ0HHHH&`wd)m#h7`4;*O_FS z69S{A7sz!?D-&y@{)rv>I9CkdVGul6N1!&x#y8o)7G@LBf`kPZtE8Mf0fKS~H3p<# zKlFU>=zUQ*eRkGwMJi@SFB{mrILlJd^DyhW@`cDjNQqqjZx;~15q#z=A~3jY8A literal 0 HcmV?d00001 diff --git a/ctr/tests/ctr128/mod.rs b/ctr/tests/ctr128/mod.rs new file mode 100644 index 0000000..c5e0206 --- /dev/null +++ b/ctr/tests/ctr128/mod.rs @@ -0,0 +1,12 @@ +use aes::{Aes128, Aes256}; +use ctr::{flavors, Ctr128BE, CtrCore}; + +cipher::stream_cipher_test!(aes128_ctr_core, "aes128-ctr", Ctr128BE); +cipher::stream_cipher_test!(aes256_ctr_core, "aes256-ctr", Ctr128BE); +cipher::stream_cipher_seek_test!(aes128_ctr_seek, Ctr128BE); +cipher::stream_cipher_seek_test!(aes256_ctr_seek, Ctr128BE); +cipher::iv_state_test!( + aes128_ctr_iv_state, + CtrCore, + apply_ks, +); diff --git a/ctr/tests/ctr32/be.rs b/ctr/tests/ctr32/be.rs new file mode 100644 index 0000000..98b1302 --- /dev/null +++ b/ctr/tests/ctr32/be.rs @@ -0,0 +1,86 @@ +//! Counter Mode with a 32-bit big endian counter + +use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek, StreamCipherSeekCore}; +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.get_core().get_block_pos(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.get_core().get_block_pos(), 4); + assert_eq!( + &buffer[..], + &hex!( + "35D14E6D3E3A279CF01E343E34E7DED36EEADB04F42E2251AB4377F257856DBA" + "0AB37657B9C2AA09762E518FC9395D5304E96C34CCD2F0A95CDE7321852D90C0" + )[..] + ); +} + +#[test] +fn counter_seek() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + ctr.seek(16); + assert_eq!(ctr.get_core().get_block_pos(), 1); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.get_core().get_block_pos(), 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.get_core().get_block_pos(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.get_core().get_block_pos(), 4); + assert_eq!( + &buffer[..], + &hex!( + "58FC849D1CF53C54C63E1B1D15CB3C8AAA335F72135585E9FF943F4DAC77CB63" + "BD1AE8716BE69C3B4D886B222B9B4E1E67548EF896A96E2746D8CA6476D8B183" + )[..] + ); +} + +cipher::iv_state_test!( + iv_state, + ctr::CtrCore, + apply_ks, +); diff --git a/ctr/tests/ctr32/le.rs b/ctr/tests/ctr32/le.rs new file mode 100644 index 0000000..21bf358 --- /dev/null +++ b/ctr/tests/ctr32/le.rs @@ -0,0 +1,96 @@ +//! Counter Mode with a 32-bit little endian counter + +use cipher::{ + consts::U16, generic_array::GenericArray, KeyIvInit, StreamCipher, StreamCipherSeek, + StreamCipherSeekCore, +}; +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.get_core().get_block_pos(), 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(16); + assert_eq!(ctr.get_core().get_block_pos(), 1); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.get_core().get_block_pos(), 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.get_core().get_block_pos(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.get_core().get_block_pos(), 4); + assert_eq!( + &buffer[..], + &hex!( + "A1E649D8B382293DC28375C42443BB6A226BAADC9E9CCA8214F56E07A4024E06" + "6355A0DA2E08FB00112FFA38C26189EE55DD5B0B130ED87096FE01B59A665A60" + )[..] + ); +} + +cipher::iv_state_test!( + iv_state, + ctr::CtrCore, + apply_ks, +); diff --git a/ctr/tests/ctr32/mod.rs b/ctr/tests/ctr32/mod.rs new file mode 100644 index 0000000..f9c17d4 --- /dev/null +++ b/ctr/tests/ctr32/mod.rs @@ -0,0 +1,9 @@ +//! 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 be; +mod le; diff --git a/ctr/tests/gost/mod.rs b/ctr/tests/gost/mod.rs new file mode 100644 index 0000000..31e5cec --- /dev/null +++ b/ctr/tests/gost/mod.rs @@ -0,0 +1,55 @@ +use cipher::{KeyIvInit, 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] +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] +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/mod.rs b/ctr/tests/mod.rs new file mode 100644 index 0000000..0f91835 --- /dev/null +++ b/ctr/tests/mod.rs @@ -0,0 +1,5 @@ +//! Counter Mode Tests + +mod ctr128; +mod ctr32; +mod gost; diff --git a/ige/CHANGELOG.md b/ige/CHANGELOG.md new file mode 100644 index 0000000..b6059ab --- /dev/null +++ b/ige/CHANGELOG.md @@ -0,0 +1,11 @@ +# 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.1.0 (2022-02-10) +- Initial release ([#2]) + +[#2]: https://github.com/RustCrypto/block-modes/pull/2 diff --git a/ige/Cargo.toml b/ige/Cargo.toml new file mode 100644 index 0000000..121a3bb --- /dev/null +++ b/ige/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ige" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +description = "Infinite Garble Extension (IGE) block cipher mode of operation" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/ige" +repository = "https://github.com/RustCrypto/block-modes" +keywords = ["crypto", "block-mode", "ciphers"] +categories = ["cryptography", "no-std"] + +[dependencies] +cipher = "0.4" + +[dev-dependencies] +aes = "0.8" +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3.3" + +[features] +default = ["block-padding"] +block-padding = ["cipher/block-padding"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/ige/LICENSE-APACHE b/ige/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/ige/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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/ige/LICENSE-MIT b/ige/LICENSE-MIT new file mode 100644 index 0000000..d19d409 --- /dev/null +++ b/ige/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2018-2022 RustCrypto Developers +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/ige/README.md b/ige/README.md new file mode 100644 index 0000000..04f4281 --- /dev/null +++ b/ige/README.md @@ -0,0 +1,60 @@ +# RustCrypto: IGE + +[![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] + +Generic implementation of the [Infinite Garble Extension][IGE] (IGE) +block cipher mode of operation. + + + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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/ige.svg +[crate-link]: https://crates.io/crates/ige +[docs-image]: https://docs.rs/ige/badge.svg +[docs-link]: https://docs.rs/ige/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-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/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/ige/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Aige+branch%3Amaster + +[//]: # (general links) + +[CBC]: https://www.links.org/files/openssl-ige.pdf +[cipher-doc]: https://docs.rs/cipher/ diff --git a/ige/benches/aes128.rs b/ige/benches/aes128.rs new file mode 100644 index 0000000..af67406 --- /dev/null +++ b/ige/benches/aes128.rs @@ -0,0 +1,16 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::block_encryptor_bench!( + KeyIv: ige::Encryptor, + ige_aes128_encrypt_block, + ige_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: ige::Decryptor, + ige_aes128_decrypt_block, + ige_aes128_decrypt_blocks, +); diff --git a/ige/src/decrypt.rs b/ige/src/decrypt.rs new file mode 100644 index 0000000..4060cf4 --- /dev/null +++ b/ige/src/decrypt.rs @@ -0,0 +1,218 @@ +use crate::{xor, IgeIvSize}; +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{sequence::Concat, ArrayLength, GenericArray}, + inout::InOut, + typenum::{Unsigned, U1}, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::{fmt, ops::Add}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// IGE mode decryptor. +#[derive(Clone)] +pub struct Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + cipher: C, + x: Block, + y: Block, +} + +impl BlockDecryptMut for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, x, y } = self; + cipher.decrypt_with_backend_mut(Closure { x, y, f }) + } +} + +impl BlockSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type BlockSize = C::BlockSize; +} + +impl InnerUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type Inner = C; +} + +impl IvSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type IvSize = IgeIvSize; +} + +impl InnerIvInit for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + let (y, x) = iv.split_at(C::BlockSize::to_usize()); + Self { + cipher, + x: GenericArray::clone_from_slice(x), + y: GenericArray::clone_from_slice(y), + } + } +} + +impl IvState for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.y.clone().concat(self.x.clone()) + } +} + +impl AlgorithmName for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ige::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ige::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Decryptor +where + C: BlockDecryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn drop(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Decryptor +where + C: BlockDecryptMut + BlockCipher + ZeroizeOnDrop, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ +} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + x: &'a mut GenericArray, + y: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { x, y, f } = self; + f.call(&mut Backend { x, y, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + x: &'a mut GenericArray, + y: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let new_y = block.clone_in(); + let mut t = new_y.clone(); + xor(&mut t, self.x); + self.backend.proc_block((&mut t).into()); + xor(&mut t, self.y); + *block.get_out() = t.clone(); + *self.x = t; + *self.y = new_y; + } +} diff --git a/ige/src/encrypt.rs b/ige/src/encrypt.rs new file mode 100644 index 0000000..c8fd965 --- /dev/null +++ b/ige/src/encrypt.rs @@ -0,0 +1,218 @@ +use crate::{xor, IgeIvSize}; +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{sequence::Concat, ArrayLength, GenericArray}, + inout::InOut, + typenum::{Unsigned, U1}, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::{fmt, ops::Add}; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// IGE mode encryptor. +#[derive(Clone)] +pub struct Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + cipher: C, + x: Block, + y: Block, +} + +impl BlockEncryptMut for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, x, y } = self; + cipher.encrypt_with_backend_mut(Closure { x, y, f }) + } +} + +impl BlockSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type BlockSize = C::BlockSize; +} + +impl InnerUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type Inner = C; +} + +impl IvSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + type IvSize = IgeIvSize; +} + +impl InnerIvInit for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + let (y, x) = iv.split_at(C::BlockSize::to_usize()); + Self { + cipher, + x: GenericArray::clone_from_slice(x), + y: GenericArray::clone_from_slice(y), + } + } +} + +impl IvState for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.y.clone().concat(self.x.clone()) + } +} + +impl AlgorithmName for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ige::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ige::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Encryptor +where + C: BlockEncryptMut + BlockCipher, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ + fn drop(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Encryptor +where + C: BlockEncryptMut + BlockCipher + ZeroizeOnDrop, + C::BlockSize: Add, + IgeIvSize: ArrayLength, +{ +} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + x: &'a mut GenericArray, + y: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { x, y, f } = self; + f.call(&mut Backend { x, y, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + x: &'a mut GenericArray, + y: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let new_x = block.clone_in(); + let mut t = new_x.clone(); + xor(&mut t, self.y); + self.backend.proc_block((&mut t).into()); + xor(&mut t, self.x); + *block.get_out() = t.clone(); + *self.x = new_x; + *self.y = t; + } +} diff --git a/ige/src/lib.rs b/ige/src/lib.rs new file mode 100644 index 0000000..b145a05 --- /dev/null +++ b/ige/src/lib.rs @@ -0,0 +1,92 @@ +//! [Infinite Garble Extension][1] (IGE) block cipher mode of operation. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +//! use hex_literal::hex; +//! +//! type Aes128IgeEnc = ige::Encryptor; +//! type Aes128IgeDec = ige::Decryptor; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 32]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "e3da005add5f05d45899f64891f7629b" +//! "6fcff7d21537fcd3569373d25701a5d1" +//! "d9e586e4c5b8ac09f2190485a76873c2" +//! ); +//! +//! // encrypt/decrypt in-place +//! // buffer must be big enough for padded plaintext +//! let mut buf = vec![0u8; 48]; +//! let pt_len = plaintext.len(); +//! buf[..pt_len].copy_from_slice(&plaintext[..]); +//! let ct = Aes128IgeEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_mut::(&mut buf, pt_len) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let pt = Aes128IgeDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_mut::(&mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! let mut buf = vec![0u8; 48]; +//! let ct = Aes128IgeEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_b2b_mut::(&plaintext[..], &mut buf) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let mut buf = vec![0u8; 48]; +//! let pt = Aes128IgeDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_b2b_mut::(&ct, &mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! ``` +//! +//! [1]: https://www.links.org/files/openssl-ige.pdf + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/ige/0.1.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +mod decrypt; +mod encrypt; + +pub use cipher; +pub use decrypt::Decryptor; +pub use encrypt::Encryptor; + +use cipher::{ + generic_array::{ArrayLength, GenericArray}, + typenum::Sum, + BlockSizeUser, +}; + +type BlockSize = ::BlockSize; +type IgeIvSize = Sum, BlockSize>; + +#[inline(always)] +fn xor>(out: &mut GenericArray, buf: &GenericArray) { + for (a, b) in out.iter_mut().zip(buf) { + *a ^= *b; + } +} diff --git a/ige/tests/aes.rs b/ige/tests/aes.rs new file mode 100644 index 0000000..813617f --- /dev/null +++ b/ige/tests/aes.rs @@ -0,0 +1,12 @@ +use aes::{Aes128, Aes128Dec, Aes128Enc}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; +use ige::{Decryptor, Encryptor}; + +iv_state_test!(aes128_ige_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes128_ige_dec_iv_state, Decryptor, decrypt); + +// Test vectors from: +block_mode_enc_test!(aes128_cbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128_cbc_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes128enc_cbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128dec_cbc_dec_test, "aes128", Decryptor); diff --git a/ige/tests/data/aes128.blb b/ige/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..f5a89c7b25c2f8194830210d187a5aa12abbdfe0 GIT binary patch literal 233 zcmZQjU|?ioW?^Mx=iubx=HcbzcR&{q5EK#?5fu}ckd%^^k(HBoU?2b-q*^7Hg;qZc zdU>nQ`Ig@oSDP8BM!GBS%5fG9CfAv3oiC)FW0HLoPGBr`ux zAwNyQ)7@1eH$Np+AuYd1!M`9iFF4p?WUaqLVA9QLS~6VQD%BZi6R#O^B+Qc literal 0 HcmV?d00001 diff --git a/ofb/CHANGELOG.md b/ofb/CHANGELOG.md new file mode 100644 index 0000000..d534cfa --- /dev/null +++ b/ofb/CHANGELOG.md @@ -0,0 +1,52 @@ +# 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.6.0 (2022-02-10) +### Changed +- Update `cipher` dependency to v0.4 and move crate +to the [RustCrypto/block-modes] repository ([#2]) + +[#2]: https://github.com/RustCrypto/block-modes/pull/2 +[RustCrypto/block-modes]: https://github.com/RustCrypto/block-modes + +## 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` 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 new file mode 100644 index 0000000..a7ee724 --- /dev/null +++ b/ofb/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ofb" +version = "0.6.0" # Also update html_root_url in lib.rs when bumping this +description = "Output Feedback][OFB] (OFB) block cipher mode of operation" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/ofb" +repository = "https://github.com/RustCrypto/block-modes" +keywords = ["crypto", "block-mode", "stream-cipher", "ciphers"] +categories = ["cryptography", "no-std"] + +[dependencies] +cipher = "0.4" + +[dev-dependencies] +aes = "0.8" +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3" + +[features] +block-padding = ["cipher/block-padding"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/ofb/LICENSE-APACHE b/ofb/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/ofb/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 new file mode 100644 index 0000000..d19d409 --- /dev/null +++ b/ofb/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2018-2022 RustCrypto Developers +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 new file mode 100644 index 0000000..44fa9b7 --- /dev/null +++ b/ofb/README.md @@ -0,0 +1,60 @@ +# RustCrypto: 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] + +Generic implementation of the [Output Feedback][OFB] (OFB) block cipher mode +of operation. + + + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/ofb/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Aofb+branch%3Amaster + +[//]: # (general links) + +[OFB]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB) +[cipher-doc]: https://docs.rs/cipher/ diff --git a/ofb/benches/aes128.rs b/ofb/benches/aes128.rs new file mode 100644 index 0000000..d3f7c70 --- /dev/null +++ b/ofb/benches/aes128.rs @@ -0,0 +1,24 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::stream_cipher_bench!( + ofb::Ofb; + ofb_aes128_stream_bench1_16b 16; + ofb_aes128_stream_bench2_256b 256; + ofb_aes128_stream_bench3_1kib 1024; + ofb_aes128_stream_bench4_16kib 16384; +); + +cipher::block_encryptor_bench!( + KeyIv: ofb::OfbCore, + ofb_aes128_encrypt_block, + ofb_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: ofb::OfbCore, + ofb_aes128_decrypt_block, + ofb_aes128_decrypt_blocks, +); diff --git a/ofb/src/backend.rs b/ofb/src/backend.rs new file mode 100644 index 0000000..b5f9566 --- /dev/null +++ b/ofb/src/backend.rs @@ -0,0 +1,114 @@ +use cipher::{ + consts::U1, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + Block, BlockBackend, BlockClosure, BlockSizeUser, ParBlocksSizeUser, StreamBackend, + StreamClosure, +}; + +pub(crate) struct Closure1<'a, BS, SC> +where + BS: ArrayLength, + SC: StreamClosure, +{ + pub(crate) iv: &'a mut GenericArray, + pub(crate) f: SC, +} + +impl<'a, BS, SC> BlockSizeUser for Closure1<'a, BS, SC> +where + BS: ArrayLength, + SC: StreamClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, SC> BlockClosure for Closure1<'a, BS, SC> +where + BS: ArrayLength, + SC: StreamClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +pub(crate) struct Closure2<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + pub(crate) iv: &'a mut GenericArray, + pub(crate) f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure2<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure2<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + self.backend.proc_block(self.iv.into()); + block.xor_in2out(self.iv); + } +} + +impl<'a, BS, BK> StreamBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn gen_ks_block(&mut self, block: &mut Block) { + self.backend.proc_block(self.iv.into()); + *block = self.iv.clone(); + } +} diff --git a/ofb/src/lib.rs b/ofb/src/lib.rs new file mode 100644 index 0000000..fe9a6e2 --- /dev/null +++ b/ofb/src/lib.rs @@ -0,0 +1,207 @@ +//! [Output feedback][1] (OFB) mode. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{KeyIvInit, StreamCipher}; +//! use hex_literal::hex; +//! +//! type Aes128Ofb = ofb::Ofb; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "3357121ebb5a29468bd861467596ce3dc6ba5df50e536a2443b8ee16c2f7cd0869c9" +//! ); +//! +//! // encrypt in-place +//! let mut buf = plaintext.to_vec(); +//! let mut cipher = Aes128Ofb::new(&key.into(), &iv.into()); +//! cipher.apply_keystream(&mut buf); +//! assert_eq!(buf, &ciphertext[..]); +//! +//! // OFB mode can be used with streaming messages +//! let mut cipher = Aes128Ofb::new(&key.into(), &iv.into()); +//! for chunk in buf.chunks_mut(3) { +//! cipher.apply_keystream(chunk); +//! } +//! assert_eq!(buf, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! // buffer length must be equal to input length +//! let mut buf1 = vec![0u8; 34]; +//! let mut cipher = Aes128Ofb::new(&key.into(), &iv.into()); +//! cipher +//! .apply_keystream_b2b(&plaintext[..], &mut buf1) +//! .unwrap(); +//! assert_eq!(buf1, &ciphertext[..]); +//! +//! let mut buf2 = vec![0u8; 34]; +//! let mut cipher = Aes128Ofb::new(&key.into(), &iv.into()); +//! cipher.apply_keystream_b2b(&buf1, &mut buf2).unwrap(); +//! assert_eq!(buf2, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_(OFB) + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/ofb/0.6.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +pub use cipher; + +mod backend; + +use cipher::{ + crypto_common::{InnerUser, IvSizeUser}, + AlgorithmName, Block, BlockCipher, BlockClosure, BlockDecryptMut, BlockEncryptMut, + BlockSizeUser, InnerIvInit, Iv, IvState, StreamCipherCore, StreamCipherCoreWrapper, + StreamClosure, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Buffered Output feedback (OFB) mode. +pub type Ofb = StreamCipherCoreWrapper>; + +/// Output feedback (OFB) mode. +#[derive(Clone)] +pub struct OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl InnerUser for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + iv: iv.clone(), + } + } +} + +impl IvState for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl StreamCipherCore for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + fn remaining_blocks(&self) -> Option { + None + } + + fn process_with_backend(&mut self, f: impl StreamClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(backend::Closure1 { iv, f }); + } +} + +impl BlockEncryptMut for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(backend::Closure2 { iv, f }) + } +} + +impl BlockDecryptMut for OfbCore +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(backend::Closure2 { iv, f }) + } +} + +impl AlgorithmName for OfbCore +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Ofb<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for OfbCore +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("OfbCore<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for OfbCore { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for OfbCore {} diff --git a/ofb/tests/aes.rs b/ofb/tests/aes.rs new file mode 100644 index 0000000..74fd5ce --- /dev/null +++ b/ofb/tests/aes.rs @@ -0,0 +1,34 @@ +use aes::*; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test, stream_cipher_test}; +use ofb::{Ofb, OfbCore}; + +iv_state_test!(aes128_ofb_enc_iv_state, OfbCore, encrypt); +iv_state_test!(aes128_ofb_dec_iv_state, OfbCore, decrypt); +iv_state_test!(aes128_ofb_apply_ks_iv_state, OfbCore, apply_ks); +iv_state_test!(aes192_ofb_enc_iv_state, OfbCore, encrypt); +iv_state_test!(aes192_ofb_dec_iv_state, OfbCore, decrypt); +iv_state_test!(aes192_ofb_apply_ks_iv_state, OfbCore, apply_ks); +iv_state_test!(aes256_ofb_enc_iv_state, OfbCore, encrypt); +iv_state_test!(aes256_ofb_dec_iv_state, OfbCore, decrypt); +iv_state_test!(aes256_ofb_apply_ks_iv_state, OfbCore, apply_ks); + +// Test vectors from CVAP "AES Multiblock Message Test (MMT) Sample Vectors": +// +block_mode_enc_test!(aes128_ofb_enc_test, "aes128", OfbCore); +block_mode_dec_test!(aes128_ofb_dec_test, "aes128", OfbCore); +block_mode_enc_test!(aes128enc_ofb_enc_test, "aes128", OfbCore); +block_mode_dec_test!(aes128dec_ofb_dec_test, "aes128", OfbCore); +stream_cipher_test!(aes128_ofb_stream_test, "aes128", Ofb); +stream_cipher_test!(aes128enc_ofb_stream_test, "aes128", Ofb); +block_mode_enc_test!(aes192_ofb_enc_test, "aes192", OfbCore); +block_mode_dec_test!(aes192_ofb_dec_test, "aes192", OfbCore); +block_mode_enc_test!(aes192enc_ofb_enc_test, "aes192", OfbCore); +block_mode_dec_test!(aes192dec_ofb_dec_test, "aes192", OfbCore); +stream_cipher_test!(aes192_ofb_stream_test, "aes192", Ofb); +stream_cipher_test!(aes192enc_ofb_stream_test, "aes192", Ofb); +block_mode_enc_test!(aes256_ofb_enc_test, "aes256", OfbCore); +block_mode_dec_test!(aes256_ofb_dec_test, "aes256", OfbCore); +block_mode_enc_test!(aes256enc_ofb_enc_test, "aes256", OfbCore); +block_mode_dec_test!(aes256dec_ofb_dec_test, "aes256", OfbCore); +stream_cipher_test!(aes256_ofb_stream_test, "aes256", Ofb); +stream_cipher_test!(aes256enc_ofb_stream_test, "aes256", Ofb); diff --git a/ofb/tests/data/aes128.blb b/ofb/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..c5d1f41f5c6513e47034b1a52009d2c780fee00b GIT binary patch literal 4269 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 X$U$hZPx_C4Q{hZDqSF#hXrh0$$pOFb literal 0 HcmV?d00001 diff --git a/pcbc/CHANGELOG.md b/pcbc/CHANGELOG.md new file mode 100644 index 0000000..b6059ab --- /dev/null +++ b/pcbc/CHANGELOG.md @@ -0,0 +1,11 @@ +# 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.1.0 (2022-02-10) +- Initial release ([#2]) + +[#2]: https://github.com/RustCrypto/block-modes/pull/2 diff --git a/pcbc/Cargo.toml b/pcbc/Cargo.toml new file mode 100644 index 0000000..35161c1 --- /dev/null +++ b/pcbc/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "pcbc" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +description = "Propagating Cipher Block Chaining (PCBC) block cipher mode of operation" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/pcbc" +repository = "https://github.com/RustCrypto/block-ciphers" +keywords = ["crypto", "block-mode", "ciphers"] +categories = ["cryptography", "no-std"] + +[dependencies] +cipher = "0.4" + +[dev-dependencies] +aes = "0.8" +cipher = { version = "0.4", features = ["dev"] } +hex-literal = "0.3.3" + +[features] +default = ["block-padding"] +block-padding = ["cipher/block-padding"] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/pcbc/LICENSE-APACHE b/pcbc/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/pcbc/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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/pcbc/LICENSE-MIT b/pcbc/LICENSE-MIT new file mode 100644 index 0000000..d19d409 --- /dev/null +++ b/pcbc/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2018-2022 RustCrypto Developers +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/pcbc/README.md b/pcbc/README.md new file mode 100644 index 0000000..ab64cf1 --- /dev/null +++ b/pcbc/README.md @@ -0,0 +1,60 @@ +# RustCrypto: PCBC + +[![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] + +Generic implementation of the [Propagating Cipher Block Chaining][PCBC] (PCBC) +block cipher mode of operation. + + + +See [documentation][cipher-doc] of the `cipher` crate for additional information. + +## Minimum Supported Rust Version + +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. + +## 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/pcbc.svg +[crate-link]: https://crates.io/crates/pcbc +[docs-image]: https://docs.rs/pcbc/badge.svg +[docs-link]: https://docs.rs/pcbc/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-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/308460-block-modes +[build-image]: https://github.com/RustCrypto/block-modes/workflows/pcbc/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-modes/actions?query=workflow%3Apcbc+branch%3Amaster + +[//]: # (general links) + +[PCBC]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC) +[cipher-doc]: https://docs.rs/cipher/ diff --git a/pcbc/benches/aes128.rs b/pcbc/benches/aes128.rs new file mode 100644 index 0000000..5abeae5 --- /dev/null +++ b/pcbc/benches/aes128.rs @@ -0,0 +1,16 @@ +#![feature(test)] +extern crate test; + +use aes::Aes128; + +cipher::block_encryptor_bench!( + KeyIv: pcbc::Encryptor, + pcbc_aes128_encrypt_block, + pcbc_aes128_encrypt_blocks, +); + +cipher::block_decryptor_bench!( + KeyIv: pcbc::Decryptor, + pcbc_aes128_decrypt_block, + pcbc_aes128_decrypt_blocks, +); diff --git a/pcbc/src/decrypt.rs b/pcbc/src/decrypt.rs new file mode 100644 index 0000000..0abc781 --- /dev/null +++ b/pcbc/src/decrypt.rs @@ -0,0 +1,181 @@ +use crate::xor; +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// PCBC mode decryptor. +#[derive(Clone)] +pub struct Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockDecryptMut for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + fn decrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.decrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl InnerUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + iv: iv.clone(), + } + } +} + +impl IvState for Decryptor +where + C: BlockDecryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("pcbc::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Decryptor +where + C: BlockDecryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("pcbc::Decryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Decryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Decryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let t = self.iv.clone(); + *self.iv = block.clone_in(); + self.backend.proc_block(block.reborrow()); + let res = block.get_out(); + xor(res, &t); + xor(self.iv, res); + } +} diff --git a/pcbc/src/encrypt.rs b/pcbc/src/encrypt.rs new file mode 100644 index 0000000..c54dc4c --- /dev/null +++ b/pcbc/src/encrypt.rs @@ -0,0 +1,181 @@ +use crate::xor; +use cipher::{ + consts::U1, + crypto_common::{InnerUser, IvSizeUser}, + generic_array::{ArrayLength, GenericArray}, + inout::InOut, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockClosure, BlockEncryptMut, BlockSizeUser, + InnerIvInit, Iv, IvState, ParBlocksSizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +/// PCBC mode encryptor. +#[derive(Clone)] +pub struct Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, +} + +impl BlockSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type BlockSize = C::BlockSize; +} + +impl BlockEncryptMut for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + fn encrypt_with_backend_mut(&mut self, f: impl BlockClosure) { + let Self { cipher, iv } = self; + cipher.encrypt_with_backend_mut(Closure { iv, f }) + } +} + +impl InnerUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + +impl IvSizeUser for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + +impl InnerIvInit for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(cipher: C, iv: &Iv) -> Self { + Self { + cipher, + iv: iv.clone(), + } + } +} + +impl IvState for Encryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn iv_state(&self) -> Iv { + self.iv.clone() + } +} + +impl AlgorithmName for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("pcbc::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + +impl fmt::Debug for Encryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("pcbc::Encryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Encryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Encryptor {} + +struct Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + iv: &'a mut GenericArray, + f: BC, +} + +impl<'a, BS, BC> BlockSizeUser for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + type BlockSize = BS; +} + +impl<'a, BS, BC> BlockClosure for Closure<'a, BS, BC> +where + BS: ArrayLength, + BC: BlockClosure, +{ + #[inline(always)] + fn call>(self, backend: &mut B) { + let Self { iv, f } = self; + f.call(&mut Backend { iv, backend }); + } +} + +struct Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + iv: &'a mut GenericArray, + backend: &'a mut BK, +} + +impl<'a, BS, BK> BlockSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type BlockSize = BS; +} + +impl<'a, BS, BK> ParBlocksSizeUser for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + type ParBlocksSize = U1; +} + +impl<'a, BS, BK> BlockBackend for Backend<'a, BS, BK> +where + BS: ArrayLength, + BK: BlockBackend, +{ + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut t = block.clone_in(); + xor(&mut t, self.iv); + *self.iv = block.clone_in(); + let b = (&t, block.get_out()).into(); + self.backend.proc_block(b); + xor(self.iv, block.get_out()); + } +} diff --git a/pcbc/src/lib.rs b/pcbc/src/lib.rs new file mode 100644 index 0000000..de6829d --- /dev/null +++ b/pcbc/src/lib.rs @@ -0,0 +1,85 @@ +//! [Propagating Cipher Block Chaining][1] (PCBC) mode. +//! +//! +//! +//! +//! Mode 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! +//! +//! # Example +//! ``` +//! use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +//! use hex_literal::hex; +//! +//! type Aes128PcbcEnc = pcbc::Encryptor; +//! type Aes128PcbcDec = pcbc::Decryptor; +//! +//! let key = [0x42; 16]; +//! let iv = [0x24; 16]; +//! let plaintext = b"hello world! this is my plaintext."; +//! let ciphertext = hex!( +//! "c7fe247ef97b21f07cbdd26cb5d346bf" +//! "ab13156d0b2f05f91c4837db5157bad5" +//! "62cb0b6fa7816e254a2fc8d852fb4315" +//! ); +//! +//! // encrypt/decrypt in-place +//! // buffer must be big enough for padded plaintext +//! let mut buf = vec![0u8; 48]; +//! let pt_len = plaintext.len(); +//! buf[..pt_len].copy_from_slice(&plaintext[..]); +//! let ct = Aes128PcbcEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_mut::(&mut buf, pt_len) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let pt = Aes128PcbcDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_mut::(&mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! +//! // encrypt/decrypt from buffer to buffer +//! let mut buf = vec![0u8; 48]; +//! let ct = Aes128PcbcEnc::new(&key.into(), &iv.into()) +//! .encrypt_padded_b2b_mut::(&plaintext[..], &mut buf) +//! .unwrap(); +//! assert_eq!(ct, &ciphertext[..]); +//! +//! let mut buf = vec![0u8; 48]; +//! let pt = Aes128PcbcDec::new(&key.into(), &iv.into()) +//! .decrypt_padded_b2b_mut::(&ct, &mut buf) +//! .unwrap(); +//! assert_eq!(pt, &plaintext[..]); +//! ``` +//! +//! [1]: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Propagating_cipher_block_chaining_(PCBC) + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_root_url = "https://docs.rs/pcbc/0.1.0" +)] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +mod decrypt; +mod encrypt; + +pub use cipher; +pub use decrypt::Decryptor; +pub use encrypt::Encryptor; + +use cipher::generic_array::{ArrayLength, GenericArray}; + +#[inline(always)] +fn xor>(out: &mut GenericArray, buf: &GenericArray) { + for (a, b) in out.iter_mut().zip(buf) { + *a ^= *b; + } +} diff --git a/pcbc/tests/aes.rs b/pcbc/tests/aes.rs new file mode 100644 index 0000000..5a18387 --- /dev/null +++ b/pcbc/tests/aes.rs @@ -0,0 +1,12 @@ +use aes::{Aes128, Aes128Dec, Aes128Enc}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; +use pcbc::{Decryptor, Encryptor}; + +iv_state_test!(aes128_pcbc_enc_iv_state, Encryptor, encrypt); +iv_state_test!(aes128_pcbc_dec_iv_state, Decryptor, decrypt); + +// The test vectors are generated using this implementation. +block_mode_enc_test!(aes128_pcbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128_pcbc_dec_test, "aes128", Decryptor); +block_mode_enc_test!(aes128enc_pcbc_enc_test, "aes128", Encryptor); +block_mode_dec_test!(aes128dec_pcbc_dec_test, "aes128", Decryptor); diff --git a/pcbc/tests/data/aes128.blb b/pcbc/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..4eedb9299254fd9752013bc7e73a16f1e8a5635b GIT binary patch literal 167 zcmZQjU|?ioW?^Mx=iubx=HcbzR}c^s5*85^6PJ*bl9rK`lW$-k1ZEbbv@bmSOn6W3 zgKeL@WvZ5VoY;BT!OkRo#i<*U9llyOFtGo7U-IxObKV5LcY=LWCQa@<-@>8F{o}}* tSuSnCDLc$V9ysf~y4k<{RO}Xxh`009gLN