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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00a442ba9e5d4c70e543b9f2ec156248c283881fcf2bb774dc592534bc0a79dc
4
- data.tar.gz: 478ebf814c22348331fcbbb5692f302425bca6cb4c48365c4508c8c4426f30ed
3
+ metadata.gz: b6a034f12d9a0dd8326bfbc7595b593cd7a0089ab7fe0c8563c7d7eecce791e9
4
+ data.tar.gz: 6161d94c79b134ffe0dd014dba44c65345910bde408ab5c618fccba456cc5356
5
5
  SHA512:
6
- metadata.gz: a291010aeaa3fce0a12479f2a8b40e0fd089125d295f1890801683350a3ffb7ea34c03ca527ed6e63738d539e305d7d46dce5294adc58b1c0494e26bf5e19835
7
- data.tar.gz: '0886502547f3628973eab880936d6bec563bb48807f39b959e7bf390433acffa67d63d30e456ed8f51a5ccb547eb1c1c3739f43f23f098581d241444d003c263'
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 then key.to_s
48
- when String then key.b
49
- else raise ArgumentError, "#{name} must be a Key or String"
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
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nuckle
4
- class CryptoError < StandardError; end
4
+ # Raised when authenticated decryption fails (MAC verification failure).
5
+ class CryptoError < StandardError
6
+ end
5
7
  end
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nuckle
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.1"
5
5
  end
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"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nuckle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger