nuckle 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/nuckle/box.rb +12 -3
- data/lib/nuckle/crypto_error.rb +3 -1
- data/lib/nuckle/private_key.rb +39 -0
- data/lib/nuckle/public_key.rb +11 -0
- data/lib/nuckle/random.rb +5 -0
- data/lib/nuckle/secret_box.rb +7 -0
- data/lib/nuckle/util.rb +10 -0
- data/lib/nuckle/version.rb +1 -1
- data/lib/nuckle.rb +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6a034f12d9a0dd8326bfbc7595b593cd7a0089ab7fe0c8563c7d7eecce791e9
|
|
4
|
+
data.tar.gz: 6161d94c79b134ffe0dd014dba44c65345910bde408ab5c618fccba456cc5356
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e513c94f45a9bf21e6a432c419868e8c5817bd82c7727debf402394dfd09d28fa488f8622ba69182e44d30e7262caa8b5ac6263e06ccddb6f7e96864da535063
|
|
7
|
+
data.tar.gz: dc9dbc2a4f101c1bc842037b88f3db9dd765020c5e3d855f6ad4c45c26847aa79e278ebcb0a410f4a4a216fd551d063b37ea5cf432996b867c031e478e9b5a00
|
data/lib/nuckle/box.rb
CHANGED
|
@@ -12,6 +12,9 @@ module Nuckle
|
|
|
12
12
|
BEFORENMBYTES = 32
|
|
13
13
|
MACBYTES = 16
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
# @param public_key [PublicKey, String] peer's 32-byte public key
|
|
17
|
+
# @param private_key [PrivateKey, String] own 32-byte private key
|
|
15
18
|
def initialize(public_key, private_key)
|
|
16
19
|
pk = extract_bytes(public_key, PUBLICKEYBYTES, "public key")
|
|
17
20
|
sk = extract_bytes(private_key, PRIVATEKEYBYTES, "private key")
|
|
@@ -25,6 +28,8 @@ module Nuckle
|
|
|
25
28
|
@secret_box = SecretBox.new(key)
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
|
|
32
|
+
# @return [Integer] required nonce length in bytes
|
|
28
33
|
def nonce_bytes = NONCEBYTES
|
|
29
34
|
|
|
30
35
|
# Encrypt plaintext with 24-byte nonce.
|
|
@@ -32,6 +37,7 @@ module Nuckle
|
|
|
32
37
|
@secret_box.encrypt(nonce, plaintext)
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
|
|
35
41
|
# Decrypt ciphertext with 24-byte nonce.
|
|
36
42
|
def decrypt(nonce, ciphertext)
|
|
37
43
|
@secret_box.decrypt(nonce, ciphertext)
|
|
@@ -44,9 +50,12 @@ module Nuckle
|
|
|
44
50
|
|
|
45
51
|
def extract_bytes(key, expected_size, name)
|
|
46
52
|
bytes = case key
|
|
47
|
-
when PublicKey, PrivateKey
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
when PublicKey, PrivateKey
|
|
54
|
+
key.to_s
|
|
55
|
+
when String
|
|
56
|
+
key.b
|
|
57
|
+
else
|
|
58
|
+
raise ArgumentError, "#{name} must be a Key or String"
|
|
50
59
|
end
|
|
51
60
|
raise ArgumentError, "#{name} must be #{expected_size} bytes (got #{bytes.bytesize})" unless bytes.bytesize == expected_size
|
|
52
61
|
|
data/lib/nuckle/crypto_error.rb
CHANGED
data/lib/nuckle/private_key.rb
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nuckle
|
|
4
|
+
# An X25519 (Curve25519) private key for Diffie-Hellman key agreement.
|
|
4
5
|
class PrivateKey
|
|
6
|
+
# Key length in bytes.
|
|
5
7
|
BYTES = 32
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
# Generates a new random private key.
|
|
11
|
+
#
|
|
12
|
+
# @return [PrivateKey]
|
|
7
13
|
def self.generate
|
|
8
14
|
new(Random.random_bytes(BYTES))
|
|
9
15
|
end
|
|
10
16
|
|
|
17
|
+
|
|
18
|
+
# @param key [String] 32-byte raw private key (binary)
|
|
11
19
|
def initialize(key)
|
|
12
20
|
key = key.to_s if key.respond_to?(:to_s) && !key.is_a?(String)
|
|
13
21
|
key = key.b
|
|
@@ -16,11 +24,42 @@ module Nuckle
|
|
|
16
24
|
@key = key
|
|
17
25
|
end
|
|
18
26
|
|
|
27
|
+
|
|
28
|
+
# Derives the corresponding public key via Curve25519 scalar base multiplication.
|
|
29
|
+
#
|
|
30
|
+
# @return [PublicKey]
|
|
19
31
|
def public_key
|
|
20
32
|
PublicKey.new(Internals::Curve25519.scalarmult_base(@key))
|
|
21
33
|
end
|
|
22
34
|
|
|
35
|
+
|
|
36
|
+
# Raw X25519 Diffie-Hellman: scalar multiply this secret key by a peer's
|
|
37
|
+
# public key to produce a 32-byte shared secret.
|
|
38
|
+
#
|
|
39
|
+
# Unlike {Box}, this returns the raw DH output without further key
|
|
40
|
+
# derivation (no HSalsa20). Callers are responsible for deriving
|
|
41
|
+
# symmetric keys from the result (e.g. via HKDF or BLAKE3-derive-key).
|
|
42
|
+
#
|
|
43
|
+
# @param peer_public_key [PublicKey, String] peer's 32-byte public key
|
|
44
|
+
# @return [String] 32-byte shared secret (binary)
|
|
45
|
+
def diffie_hellman(peer_public_key)
|
|
46
|
+
pk = case peer_public_key
|
|
47
|
+
when PublicKey
|
|
48
|
+
peer_public_key.to_s
|
|
49
|
+
when String
|
|
50
|
+
peer_public_key.b
|
|
51
|
+
else
|
|
52
|
+
raise ArgumentError, "peer_public_key must be a PublicKey or String"
|
|
53
|
+
end
|
|
54
|
+
raise ArgumentError, "peer public key must be 32 bytes" unless pk.bytesize == BYTES
|
|
55
|
+
|
|
56
|
+
Internals::Curve25519.scalarmult(@key, pk)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# @return [String] raw 32-byte key (binary)
|
|
23
61
|
def to_bytes = @key
|
|
62
|
+
# @return [String] raw 32-byte key (binary)
|
|
24
63
|
def to_s = @key
|
|
25
64
|
end
|
|
26
65
|
end
|
data/lib/nuckle/public_key.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nuckle
|
|
4
|
+
# An X25519 (Curve25519) public key.
|
|
4
5
|
class PublicKey
|
|
6
|
+
# Key length in bytes.
|
|
5
7
|
BYTES = 32
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
# @param key [String] 32-byte raw public key (binary)
|
|
7
11
|
def initialize(key)
|
|
8
12
|
key = key.to_s if key.respond_to?(:to_s) && !key.is_a?(String)
|
|
9
13
|
key = key.b
|
|
@@ -12,9 +16,16 @@ module Nuckle
|
|
|
12
16
|
@key = key
|
|
13
17
|
end
|
|
14
18
|
|
|
19
|
+
|
|
20
|
+
# @return [String] raw 32-byte key (binary)
|
|
15
21
|
def to_bytes = @key
|
|
22
|
+
# @return [String] raw 32-byte key (binary)
|
|
16
23
|
def to_s = @key
|
|
17
24
|
|
|
25
|
+
# Constant-time equality comparison.
|
|
26
|
+
#
|
|
27
|
+
# @param other [PublicKey] key to compare
|
|
28
|
+
# @return [Boolean]
|
|
18
29
|
def ==(other)
|
|
19
30
|
other.is_a?(PublicKey) && Util.verify32(@key, other.to_s)
|
|
20
31
|
end
|
data/lib/nuckle/random.rb
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module Nuckle
|
|
6
|
+
# Cryptographically secure random byte generation.
|
|
6
7
|
module Random
|
|
8
|
+
# Generates +n+ cryptographically secure random bytes.
|
|
9
|
+
#
|
|
10
|
+
# @param n [Integer] number of bytes
|
|
11
|
+
# @return [String] random bytes (binary)
|
|
7
12
|
def self.random_bytes(n)
|
|
8
13
|
SecureRandom.random_bytes(n)
|
|
9
14
|
end
|
data/lib/nuckle/secret_box.rb
CHANGED
|
@@ -12,6 +12,8 @@ module Nuckle
|
|
|
12
12
|
BOXZEROBYTES = 16
|
|
13
13
|
MACBYTES = 16
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
# @param key [String] 32-byte symmetric key (binary)
|
|
15
17
|
def initialize(key)
|
|
16
18
|
key = key.b
|
|
17
19
|
raise ArgumentError, "key must be #{KEYBYTES} bytes (got #{key.bytesize})" unless key.bytesize == KEYBYTES
|
|
@@ -19,7 +21,10 @@ module Nuckle
|
|
|
19
21
|
@key = key
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
|
|
25
|
+
# @return [Integer] required nonce length in bytes
|
|
22
26
|
def nonce_bytes = NONCEBYTES
|
|
27
|
+
# @return [Integer] required key length in bytes
|
|
23
28
|
def key_bytes = KEYBYTES
|
|
24
29
|
|
|
25
30
|
# Encrypt plaintext with 24-byte nonce.
|
|
@@ -47,6 +52,7 @@ module Nuckle
|
|
|
47
52
|
mac + c.byteslice(ZEROBYTES..)
|
|
48
53
|
end
|
|
49
54
|
|
|
55
|
+
|
|
50
56
|
# Decrypt ciphertext with 24-byte nonce.
|
|
51
57
|
#
|
|
52
58
|
# @param nonce [String] 24-byte nonce
|
|
@@ -76,6 +82,7 @@ module Nuckle
|
|
|
76
82
|
m.byteslice(ZEROBYTES..)
|
|
77
83
|
end
|
|
78
84
|
|
|
85
|
+
|
|
79
86
|
# Aliases matching rbnacl API
|
|
80
87
|
alias box encrypt
|
|
81
88
|
alias open decrypt
|
data/lib/nuckle/util.rb
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Nuckle
|
|
4
|
+
# Constant-time comparison utilities for cryptographic data.
|
|
4
5
|
module Util
|
|
5
6
|
# Compare two 16-byte strings.
|
|
6
7
|
def self.verify16(a, b)
|
|
7
8
|
verify(a, b, 16)
|
|
8
9
|
end
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
# Compare two 32-byte strings.
|
|
11
13
|
def self.verify32(a, b)
|
|
12
14
|
verify(a, b, 32)
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
|
|
15
18
|
# Compare two 64-byte strings.
|
|
16
19
|
def self.verify64(a, b)
|
|
17
20
|
verify(a, b, 64)
|
|
18
21
|
end
|
|
19
22
|
|
|
23
|
+
|
|
24
|
+
# Compares two binary strings for equality.
|
|
25
|
+
#
|
|
26
|
+
# @param a [String] first string
|
|
27
|
+
# @param b [String] second string
|
|
28
|
+
# @param expected_size [Integer, nil] required byte length, or nil to skip check
|
|
29
|
+
# @return [Boolean]
|
|
20
30
|
def self.verify(a, b, expected_size = nil)
|
|
21
31
|
a = a.b if a.encoding != Encoding::BINARY
|
|
22
32
|
b = b.b if b.encoding != Encoding::BINARY
|
data/lib/nuckle/version.rb
CHANGED
data/lib/nuckle.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Pure Ruby NaCl-compatible cryptography library.
|
|
4
|
+
# Provides Curve25519 key agreement, XSalsa20-Poly1305 authenticated
|
|
5
|
+
# encryption (SecretBox), and public-key authenticated encryption (Box).
|
|
6
|
+
|
|
3
7
|
require_relative "nuckle/version"
|
|
4
8
|
require_relative "nuckle/crypto_error"
|
|
5
9
|
require_relative "nuckle/random"
|