-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5e722d2
commit a1a08d7
Showing
12 changed files
with
20,844 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{benchmarks,config,lib,test}/**/*.{ex,exs}"] | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# The directory Mix will write compiled artifacts to. | ||
/_build/ | ||
|
||
# If you run "mix test --cover", coverage assets end up here. | ||
/cover/ | ||
|
||
# The directory Mix downloads your dependencies sources to. | ||
/deps/ | ||
|
||
# Where third-party dependencies like ExDoc output generated docs. | ||
/doc/ | ||
|
||
# Ignore .fetch files in case you like to edit your project deps locally. | ||
/.fetch | ||
|
||
# If the VM crashes, it generates a dump, let's ignore it too. | ||
erl_crash.dump | ||
|
||
# Also ignore archive artifacts (built via "mix archive.build"). | ||
*.ez | ||
|
||
# Ignore package tarball (built via "mix hex.build"). | ||
ctr_drbg-*.tar | ||
|
||
# Temporary files, for example, from tests. | ||
/tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Ben Youngblood | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# CtrDrbg [![Hex Version](https://img.shields.io/hexpm/v/ctr_drbg.svg)](https://hex.pm/packages/ctr_drbg) [![Hex Docs](https://img.shields.io/badge/docs-hexpm-blue.svg)](https://hexdocs.pm/ctr_drbg/) | ||
|
||
A pure Elixir implementation of the CTR_DRBG PRNG algorithm. | ||
|
||
## Supported Functionality | ||
|
||
- Ciphers | ||
- [x] AES-128 | ||
- [ ] AES-192 | ||
- [ ] AES-256 | ||
- [ ] Triple DES | ||
- [x] Personalization string | ||
- [ ] Reseeding counter | ||
- [ ] Prediction resistance | ||
- [ ] Derivation function | ||
- [ ] Security strength option | ||
|
||
## Installation | ||
|
||
Add `ctr_drbg` to your list of dependencies in `mix.exs`: | ||
|
||
```elixir | ||
def deps do | ||
[ | ||
{:ctr_drbg, "~> 0.1.0"} | ||
] | ||
end | ||
``` | ||
|
||
Docs can be found at <https://hexdocs.pm/ctr_drbg>. | ||
|
||
## Tests | ||
|
||
The test vectors under `text/fixtures` were retrieved from the [NIST's Cryptographic | ||
Algorithm Validation Program](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/random-number-generators#DRBG) (`drbgvectors_pr_false/CTR_DRBG.rsp` in the `drbgtestvectors.zip` archive). | ||
|
||
## Benchmarks | ||
|
||
```text | ||
$> mix benchmark | ||
Generated ctr_drbg app | ||
Operating System: macOS | ||
CPU Information: Apple M1 Max | ||
Number of Available Cores: 10 | ||
Available memory: 32 GB | ||
Elixir 1.15.4 | ||
Erlang 26.0.2 | ||
Benchmark suite executing with the following configuration: | ||
warmup: 2 s | ||
time: 5 s | ||
memory time: 0 ns | ||
reduction time: 0 ns | ||
parallel: 1 | ||
inputs: 16 bytes, 32 bytes, 64 bytes | ||
Estimated total run time: 21 s | ||
Benchmarking CtrDrbg.generate/2 with input 16 bytes ... | ||
Benchmarking CtrDrbg.generate/2 with input 32 bytes ... | ||
Benchmarking CtrDrbg.generate/2 with input 64 bytes ... | ||
##### With input 16 bytes ##### | ||
Name ips average deviation median 99th % | ||
CtrDrbg.generate/2 427.59 K 2.34 μs ±2044.84% 1.54 μs 3.58 μs | ||
##### With input 32 bytes ##### | ||
Name ips average deviation median 99th % | ||
CtrDrbg.generate/2 401.58 K 2.49 μs ±978.15% 1.92 μs 4.79 μs | ||
##### With input 64 bytes ##### | ||
Name ips average deviation median 99th % | ||
CtrDrbg.generate/2 278.11 K 3.60 μs ±558.63% 2.83 μs 7.25 μs | ||
``` |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
{opts, _, _} = OptionParser.parse(System.argv(), strict: [save: :boolean, profile: :string]) | ||
|
||
:rand.seed({:exsss, [203_174_340_550_522_226 | 57_531_746_987_733_918]}) | ||
|
||
initial_state = CtrDrbg.init(:rand.bytes(16)) | ||
|
||
config = [ | ||
load: Path.join(__DIR__, "*.benchee"), | ||
inputs: %{ | ||
"16 bytes" => %{bytes: 16, state: initial_state}, | ||
"32 bytes" => %{bytes: 32, state: initial_state}, | ||
"64 bytes" => %{bytes: 64, state: initial_state} | ||
} | ||
] | ||
|
||
config = | ||
if opts[:save] do | ||
tag = [?v | Application.spec(:ctr_drbg, :vsn)] |> to_string() | ||
[save: [path: Path.join(__DIR__, "benchmark.benchee"), tag: tag]] ++ config | ||
else | ||
config | ||
end | ||
|
||
config = | ||
case opts[:profile] do | ||
nil -> config | ||
"cprof" -> [profile: :cprof] ++ config | ||
"eprof" -> [profile: :eprof] ++ config | ||
"fprof" -> [profile: :fprof] ++ config | ||
_ -> [profile: true] ++ config | ||
end | ||
|
||
config | ||
|> Benchee.init() | ||
|> Benchee.system() | ||
|> Benchee.benchmark( | ||
"CtrDrbg.generate/2", | ||
fn %{state: state, bytes: bytes} -> CtrDrbg.generate(state, bytes) end | ||
) | ||
|> Benchee.collect() | ||
|> Benchee.statistics() | ||
|> Benchee.load() | ||
|> Benchee.relative_statistics() | ||
|> Benchee.Formatter.output() | ||
|> Benchee.profile() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
defmodule CtrDrbg do | ||
@moduledoc """ | ||
Pure Elixir implementation of the CTR_DRBG PRNG algorithm. | ||
## Examples | ||
iex> state = CtrDrbg.init(:crypto.strong_rand_bytes(16)) | ||
iex> {state, random_bytes} = CtrDrbg.generate(state, 32) | ||
{#CtrDrbg<...>, <<...>>} | ||
""" | ||
|
||
@typedoc "The current PRNG state." | ||
@opaque t :: %__MODULE__{} | ||
|
||
@derive {Inspect, only: []} | ||
@enforce_keys [:k, :v] | ||
defstruct [:k, :v] | ||
|
||
@block_size 16 | ||
@seed_size 32 | ||
|
||
@doc """ | ||
Initialize and seed the generator. | ||
""" | ||
@spec init(binary(), binary()) :: t() | ||
def init(entropy, pstring \\ <<>>) do | ||
seed = exor(entropy, pstring) | ||
|
||
reseed( | ||
%__MODULE__{v: null_pad(16), k: null_pad(16)}, | ||
seed | ||
) | ||
end | ||
|
||
@doc """ | ||
Reseed the generator. | ||
""" | ||
@spec reseed(t(), binary(), binary()) :: t() | ||
def reseed(state, seed, additional_entropy_input \\ <<>>) do | ||
seed = exor(seed, additional_entropy_input) | ||
update(state, seed) | ||
end | ||
|
||
@doc """ | ||
Generate an arbitrary number of random bytes. | ||
""" | ||
@spec generate(t(), pos_integer(), binary()) :: {t(), binary()} | ||
def generate(state, length \\ 16, additional_entropy_input \\ <<>>) do | ||
state = | ||
if byte_size(additional_entropy_input) > 0 do | ||
update(state, additional_entropy_input) | ||
else | ||
state | ||
end | ||
|
||
output_blocks = ceil(length / @block_size) | ||
|
||
{v, output} = next(output_blocks, state.k, state.v) | ||
|
||
state = %{state | v: v} | ||
|
||
{update(state, additional_entropy_input), binary_slice(output, 0..(length - 1))} | ||
end | ||
|
||
defp update(state, seed) do | ||
# for each 16-byte block of seed: | ||
# increment V | ||
# encrypt V using K | ||
# pass V to the next iteration and append the output to the output block | ||
|
||
{_v, output} = next(ceil(@seed_size / @block_size), state.k, state.v) | ||
|
||
# XOR output with seed | ||
output = exor(output, seed) | ||
|
||
# K = first half of output | ||
# V = second half of output | ||
<<k::binary-size(16), v::binary-size(16)>> = output | ||
|
||
%{state | k: k, v: v} | ||
end | ||
|
||
defp increment(bin) do | ||
<<int_val::unit(8)-size(byte_size(bin))>> = bin | ||
<<int_val + 1::128>> | ||
end | ||
|
||
defp next(blocks, k, v, acc \\ <<>>) | ||
|
||
defp next(0, _k, v, acc) do | ||
{v, acc} | ||
end | ||
|
||
defp next(blocks, k, v, acc) do | ||
v = increment(v) | ||
block = :crypto.crypto_one_time(:aes_128_ecb, k, v, true) | ||
next(blocks - 1, k, v, acc <> block) | ||
end | ||
|
||
defp null_pad(binary \\ <<>>, length, direction \\ :leading) | ||
|
||
defp null_pad(binary, length, _direction) when byte_size(binary) >= length, do: binary | ||
|
||
defp null_pad(binary, length, direction) do | ||
filler = :binary.copy(<<0>>, length - byte_size(binary)) | ||
|
||
case direction do | ||
:leading -> filler <> binary | ||
:trailing -> binary <> filler | ||
end | ||
end | ||
|
||
# Perform a bitwise exor on bin1 and bin2. If the two binaries are not the same | ||
# size, pad the smaller one with trailing null bytes (X ^ 0 == X). If either binary | ||
# is empty, return the other. | ||
defp exor(bin1, bin2) when byte_size(bin1) == 0, do: bin2 | ||
defp exor(bin1, bin2) when byte_size(bin2) == 0, do: bin1 | ||
|
||
defp exor(bin1, bin2) when byte_size(bin1) < byte_size(bin2) do | ||
bin1 = null_pad(bin1, byte_size(bin2), :trailing) | ||
exor(bin1, bin2) | ||
end | ||
|
||
defp exor(bin1, bin2) when byte_size(bin1) > byte_size(bin2) do | ||
bin2 = null_pad(bin2, byte_size(bin1), :trailing) | ||
exor(bin1, bin2) | ||
end | ||
|
||
defp exor(bin1, bin2) when byte_size(bin1) == byte_size(bin2) do | ||
:crypto.exor(bin1, bin2) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
defmodule CtrDrbg.MixProject do | ||
use Mix.Project | ||
|
||
@version "0.1.0" | ||
@source_url "https://github.com/bjyoungblood/ctr_drbg" | ||
|
||
def project do | ||
[ | ||
app: :ctr_drbg, | ||
version: @version, | ||
elixir: "~> 1.15", | ||
start_permanent: Mix.env() == :prod, | ||
deps: deps(), | ||
aliases: [ | ||
benchmark: "run benchmarks/benchmark.exs" | ||
], | ||
description: "A pure Elixir implementation of the CTR_DRBG PRNG algorithm.", | ||
source_url: @source_url, | ||
dialyzer: dialyzer(), | ||
package: package(), | ||
docs: docs() | ||
] | ||
end | ||
|
||
def application do | ||
[ | ||
extra_applications: [:logger] | ||
] | ||
end | ||
|
||
defp deps do | ||
[ | ||
{:benchee, "~> 1.0", only: [:dev, :test]}, | ||
{:dialyxir, "~> 1.3", only: [:dev, :test], runtime: false}, | ||
{:ex_doc, "~> 0.30", only: :dev, runtime: false} | ||
] | ||
end | ||
|
||
defp package do | ||
[ | ||
licenses: ["MIT"], | ||
links: %{"GitHub" => "https://github.com/smartrent/grizzly"} | ||
] | ||
end | ||
|
||
defp dialyzer() do | ||
ci_opts = | ||
if System.get_env("CI") do | ||
[plt_core_path: "_build/plts", plt_local_path: "_build/plts"] | ||
else | ||
[] | ||
end | ||
|
||
[ | ||
flags: [:unmatched_returns, :error_handling, :missing_return, :extra_return] | ||
] ++ ci_opts | ||
end | ||
|
||
defp docs do | ||
[ | ||
main: "README", | ||
extras: ["README.md"], | ||
source_ref: "v#{@version}", | ||
source_url: @source_url | ||
] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
%{ | ||
"benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, | ||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, | ||
"dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, | ||
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, | ||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, | ||
"ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"}, | ||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, | ||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, | ||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, | ||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, | ||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, | ||
} |
Oops, something went wrong.