Skip to content

Commit 15a965a

Browse files
committed
init
0 parents  commit 15a965a

File tree

8 files changed

+275
-0
lines changed

8 files changed

+275
-0
lines changed

.github/workflows/test.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: erlef/setup-beam@v1
16+
with:
17+
otp-version: "27.1.2"
18+
gleam-version: "1.7.0"
19+
rebar3-version: "3"
20+
# elixir-version: "1"
21+
- run: gleam deps download
22+
- run: gleam test
23+
- run: gleam format --check src test

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.beam
2+
*.ez
3+
/build
4+
erl_crash.dump

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# pbkdf2
2+
3+
[![Package Version](https://img.shields.io/hexpm/v/pbkdf2)](https://hex.pm/packages/pbkdf2)
4+
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/pbkdf2/)
5+
6+
A Gleam implementation of PBKDF2 (Password-Based Key Derivation Function 2) for Erlang.
7+
8+
Currently a work in progress!
9+
10+
```sh
11+
gleam add pbkdf2@1
12+
```
13+
```gleam
14+
import pbkdf2
15+
16+
pub fn main() {
17+
// TODO: An example of the project in use
18+
}
19+
```
20+
21+
Further documentation can be found at <https://hexdocs.pm/pbkdf2>.
22+
23+
## Development
24+
25+
```sh
26+
gleam run # Run the project
27+
gleam test # Run the tests
28+
```

gleam.toml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name = "pbkdf2"
2+
version = "1.0.0"
3+
4+
# Fill out these fields if you intend to generate HTML documentation or publish
5+
# your project to the Hex package manager.
6+
#
7+
# description = ""
8+
# licences = ["Apache-2.0"]
9+
# repository = { type = "github", user = "", repo = "" }
10+
# links = [{ title = "Website", href = "" }]
11+
#
12+
# For a full reference of all the available options, you can have a look at
13+
# https://gleam.run/writing-gleam/gleam-toml/.
14+
15+
[dependencies]
16+
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
17+
gleam_crypto = ">= 1.4.0 and < 2.0.0"
18+
19+
[dev-dependencies]
20+
gleeunit = ">= 1.0.0 and < 2.0.0"

manifest.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file was generated by Gleam
2+
# You typically do not need to edit this file
3+
4+
packages = [
5+
{ name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" },
6+
{ name = "gleam_stdlib", version = "0.54.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "723BA61A2BAE8D67406E59DD88CEA1B3C3F266FC8D70F64BE9FEC81B4505B927" },
7+
{ name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" },
8+
]
9+
10+
[requirements]
11+
gleam_crypto = { version = ">= 1.4.0 and < 2.0.0" }
12+
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
13+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }

src/extern.erl

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-module(extern).
2+
3+
-export([get_salt/0]).
4+
5+
get_salt() ->
6+
<<Num:32/integer>> = crypto:strong_rand_bytes(4),
7+
BytesNum = Num rem (1024 - 64) + 64,
8+
crypto:strong_rand_bytes(BytesNum).

src/pbkdf2.gleam

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import gleam/bit_array
2+
import gleam/bytes_tree.{type BytesTree}
3+
import gleam/crypto.{type HashAlgorithm}
4+
import gleam/int
5+
import gleam/io
6+
7+
pub type Pbkdf2Keys {
8+
Pbkdf2Keys(raw: BitArray, base64: String)
9+
}
10+
11+
pub fn main() {
12+
io.println("Hello from pbkdf2!")
13+
let hash = sha256("password")
14+
io.debug(hash)
15+
}
16+
17+
pub fn sha256(password: String, salt: BitArray) -> Pbkdf2Keys {
18+
let raw = with_defaults(crypto.Sha256, password, salt, 600_000)
19+
Pbkdf2Keys(raw, bit_array.base64_encode(raw, True))
20+
}
21+
22+
pub fn hash(
23+
alg: HashAlgorithm,
24+
password: String,
25+
salt: BitArray,
26+
iterations: Int,
27+
d_len: Int,
28+
) {
29+
todo as "Should check alg is allowed and d_len not too long, then run compute_key as normal"
30+
}
31+
32+
@external(erlang, "extern", "get_salt")
33+
pub fn get_salt() -> BitArray
34+
35+
fn with_defaults(
36+
alg: HashAlgorithm,
37+
password: String,
38+
salt: BitArray,
39+
iterations: Int,
40+
) {
41+
let prf = new_prf(alg)
42+
let d_len = prf(<<"derived":utf8>>, <<"length":utf8>>) |> bit_array.byte_size
43+
44+
compute_key(
45+
prf,
46+
bit_array.from_string(password),
47+
salt,
48+
iterations,
49+
d_len,
50+
1,
51+
bytes_tree.new(),
52+
)
53+
}
54+
55+
fn compute_key(
56+
prf,
57+
password: BitArray,
58+
salt: BitArray,
59+
iterations: Int,
60+
d_len: Int,
61+
block_idx: Int,
62+
acc: BytesTree,
63+
) {
64+
case bytes_tree.byte_size(acc) > d_len {
65+
True -> {
66+
let bit_len = d_len * 8
67+
let assert <<key:bits-size(bit_len), _rest:bits>> =
68+
bytes_tree.to_bit_array(acc)
69+
key
70+
}
71+
False -> {
72+
let block =
73+
compute_block(
74+
prf,
75+
password,
76+
salt,
77+
iterations,
78+
block_idx,
79+
1,
80+
bytes_tree.new(),
81+
bytes_tree.new(),
82+
)
83+
compute_key(
84+
prf,
85+
password,
86+
salt,
87+
iterations,
88+
d_len,
89+
block_idx + 1,
90+
bytes_tree.prepend_tree(to: acc, prefix: block),
91+
)
92+
}
93+
}
94+
}
95+
96+
fn compute_block(
97+
prf,
98+
password: BitArray,
99+
salt: BitArray,
100+
iterations: Int,
101+
block_idx: Int,
102+
count: Int,
103+
prev: BytesTree,
104+
acc: BytesTree,
105+
) {
106+
case count {
107+
count if count > iterations -> acc
108+
1 -> {
109+
let init =
110+
prf(password, <<salt:bits, block_idx:int-big-size(32)>>)
111+
|> bytes_tree.from_bit_array
112+
compute_block(prf, password, salt, iterations, block_idx, 2, init, init)
113+
}
114+
_ -> {
115+
let next =
116+
prf(password, bytes_tree.to_bit_array(prev))
117+
|> bytes_tree.from_bit_array
118+
compute_block(
119+
prf,
120+
password,
121+
salt,
122+
iterations,
123+
block_idx,
124+
count + 1,
125+
next,
126+
xor(next, acc),
127+
)
128+
}
129+
}
130+
}
131+
132+
fn new_prf(alg: HashAlgorithm) {
133+
fn(key: BitArray, data: BitArray) {
134+
crypto.new_hasher(alg)
135+
|> crypto.hash_chunk(key)
136+
|> crypto.hash_chunk(data)
137+
|> crypto.digest
138+
}
139+
}
140+
141+
fn max_key_length() -> Int {
142+
int.bitwise_shift_left(1, 32) |> int.subtract(1)
143+
}
144+
145+
fn d_len_too_long(hash_len: Int, d_len: Int) -> Bool {
146+
let max_len =
147+
max_key_length()
148+
|> int.multiply(hash_len)
149+
d_len > max_len
150+
}
151+
152+
fn allowed_algorithm(alg: HashAlgorithm) -> Result(HashAlgorithm, String) {
153+
case alg {
154+
crypto.Md5 ->
155+
Error(
156+
"Insecure algorithm. Please select Sha224, Sha256, Sha384, or Sha512.",
157+
)
158+
crypto.Sha1 ->
159+
Error(
160+
"Insecure algorithm. Please select Sha224, Sha256, Sha384, or Sha512.",
161+
)
162+
_ -> Ok(alg)
163+
}
164+
}
165+
166+
@external(erlang, "crypto", "exor")
167+
fn xor(a: BytesTree, b: BytesTree) -> BytesTree

test/pbkdf2_test.gleam

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import gleeunit
2+
import gleeunit/should
3+
4+
pub fn main() {
5+
gleeunit.main()
6+
}
7+
8+
// gleeunit test functions end in `_test`
9+
pub fn hello_world_test() {
10+
1
11+
|> should.equal(1)
12+
}

0 commit comments

Comments
 (0)