phylax 0.1.0 → 0.1.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/README.md +11 -155
- data/lib/phylax.rb +4 -252
- metadata +10 -86
- data/CHANGELOG.md +0 -23
- data/ext/phylax/extconf.rb +0 -20
- data/ext/phylax/phylax.c +0 -1024
- data/lib/phylax/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8a97badaa44fd356a5e414446dc937fa8796a5e9b9a670cc8d43f76048ee275
|
|
4
|
+
data.tar.gz: bbc4c29d490c2f4599a49b2fd055c8abf78d5c771b37e9135ca76dd1236c0a49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 598b3a11263052f1fbd66dbe5eacb78924c470c312fb8484ab08c84998c15eb8828776c0f753e3146f2e8c98cf32a8c08b7462d74b57142f5f3768b7cd50493c
|
|
7
|
+
data.tar.gz: 1d17e2334c63d1c5d0a9e3257faaf93586a447dc859cd730ecb00c9adce40b4b75c6701f75852c28e26d86f0cc896fec3b88be5bf22c81077eb4791b0995d7fd
|
data/README.md
CHANGED
|
@@ -1,155 +1,11 @@
|
|
|
1
|
-
# phylax
|
|
2
|
-
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **Authenticated encryption that can't be nonce-reused.** `SecretBox` (AES-256-GCM)
|
|
14
|
-
generates a fresh 96-bit nonce on every `seal` and frames it into the output, so
|
|
15
|
-
the catastrophic GCM failure mode — reusing a `(key, nonce)` pair — is impossible
|
|
16
|
-
through the public API. There is no nonce parameter to get wrong.
|
|
17
|
-
- **Decryption is all-or-nothing.** `open` verifies the authentication tag inside the
|
|
18
|
-
OS call and raises `Phylax::AuthenticationError` rather than ever returning
|
|
19
|
-
unauthenticated plaintext.
|
|
20
|
-
- **No silent weakening.** A key that isn't exactly 32 bytes is a loud error, not a
|
|
21
|
-
silently-truncated AES-128 key. Only SHA-2 is accepted — `:md5`/`:sha1` raise.
|
|
22
|
-
- **Constant-time comparison** is provided so you don't verify MACs with `==`.
|
|
23
|
-
|
|
24
|
-
| What | API |
|
|
25
|
-
|---|---|
|
|
26
|
-
| Secure random | `Phylax.random_bytes(n)` |
|
|
27
|
-
| Hashing (one-shot) | `Phylax.sha256(data)`, `.sha384`, `.sha512` |
|
|
28
|
-
| Hashing (streaming) | `Phylax::Digest.new(:sha256)` |
|
|
29
|
-
| HMAC (one-shot) | `Phylax.hmac_sha256(key, data)`, `.hmac_sha384`, `.hmac_sha512` |
|
|
30
|
-
| HMAC (streaming) | `Phylax::HMAC.new(:sha256, key)`, `Phylax::HMAC.verify(...)` |
|
|
31
|
-
| Key derivation | `Phylax.pbkdf2(password:, salt:, iterations:, length:, hash:)` |
|
|
32
|
-
| Authenticated encryption | `Phylax::SecretBox` (AES-256-GCM) |
|
|
33
|
-
| Constant-time compare | `Phylax.secure_compare(a, b)` |
|
|
34
|
-
| Secrets at rest | `Phylax.protect(data, scope:, entropy:)` / `Phylax.unprotect` |
|
|
35
|
-
|
|
36
|
-
## Requirements
|
|
37
|
-
|
|
38
|
-
- **Windows** with a native **MSVC (mswin)** Ruby. Not supported on MinGW/UCRT.
|
|
39
|
-
- Visual Studio 2017+ / Build Tools with the **Desktop development with C++** workload.
|
|
40
|
-
|
|
41
|
-
## Install
|
|
42
|
-
|
|
43
|
-
```sh
|
|
44
|
-
gem install phylax
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Authenticated encryption — `SecretBox`
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
require "phylax"
|
|
51
|
-
|
|
52
|
-
key = Phylax::SecretBox.generate_key # 32 cryptographically random bytes
|
|
53
|
-
box = Phylax::SecretBox.new(key)
|
|
54
|
-
|
|
55
|
-
sealed = box.seal("attack at dawn") # => nonce ‖ ciphertext ‖ tag (binary)
|
|
56
|
-
box.open(sealed) # => "attack at dawn"
|
|
57
|
-
|
|
58
|
-
# Additional authenticated data (authenticated, not encrypted) — must match.
|
|
59
|
-
sealed = box.seal(payload, aad: "v1:user-42")
|
|
60
|
-
box.open(sealed, aad: "v1:user-42") # ok
|
|
61
|
-
box.open(sealed, aad: "v1:user-99") # raises Phylax::AuthenticationError
|
|
62
|
-
|
|
63
|
-
# Any tampering with the nonce, ciphertext, tag, or aad fails closed:
|
|
64
|
-
box.open(sealed.tap { |s| s.setbyte(20, s.getbyte(20) ^ 1) })
|
|
65
|
-
# => raises Phylax::AuthenticationError (no plaintext is returned)
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Derive the key from a password instead (PBKDF2-HMAC-SHA256):
|
|
69
|
-
|
|
70
|
-
```ruby
|
|
71
|
-
salt = Phylax.random_bytes(16) # store this alongside the ciphertext
|
|
72
|
-
box = Phylax::SecretBox.from_password("correct horse battery staple",
|
|
73
|
-
salt: salt, iterations: 600_000)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Wire format.** `seal` returns `nonce (12 bytes) ‖ ciphertext (= plaintext length) ‖ tag (16 bytes)`,
|
|
77
|
-
so the overhead is a constant `Phylax::SecretBox::OVERHEAD` (28) bytes. This is
|
|
78
|
-
standard AES-256-GCM: a blob from `seal` can be decrypted by any GCM
|
|
79
|
-
implementation (e.g. OpenSSL) given the same key, and vice versa.
|
|
80
|
-
|
|
81
|
-
## Hashing and HMAC
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
Phylax.sha256("data") # => 32 raw bytes (ASCII-8BIT)
|
|
85
|
-
Phylax.sha256("data").unpack1("H*") # => hex
|
|
86
|
-
|
|
87
|
-
# Streaming — non-destructive #digest, so you can keep updating:
|
|
88
|
-
d = Phylax::Digest.new(:sha256)
|
|
89
|
-
d << "chunk one" << "chunk two"
|
|
90
|
-
d.hexdigest
|
|
91
|
-
|
|
92
|
-
Phylax.hmac_sha256(key, "message") # keyed MAC, raw bytes
|
|
93
|
-
|
|
94
|
-
# Verify a MAC the safe way (constant-time, never raises on mismatch):
|
|
95
|
-
Phylax::HMAC.verify(:sha256, key, message, received_tag) # => true / false
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Key derivation, random, and comparison
|
|
99
|
-
|
|
100
|
-
```ruby
|
|
101
|
-
Phylax.pbkdf2(password: pw, salt: salt, iterations: 600_000, length: 32)
|
|
102
|
-
Phylax.random_bytes(16) # system CSPRNG (CTR_DRBG)
|
|
103
|
-
Phylax.secure_compare(tag_a, tag_b) # constant-time for equal-length inputs
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Secrets at rest — DPAPI
|
|
107
|
-
|
|
108
|
-
`protect` encrypts data with a key the OS derives from the current user (default)
|
|
109
|
-
or the machine, so the blob can only be read back on the same machine (and, for
|
|
110
|
-
`:user` scope, by the same user). Great for storing a credential or a `SecretBox`
|
|
111
|
-
key on disk without a master password.
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
blob = Phylax.protect("api-token-value") # bound to this Windows user
|
|
115
|
-
Phylax.unprotect(blob) # => "api-token-value"
|
|
116
|
-
|
|
117
|
-
# Optional second secret ("entropy") that must be supplied to unprotect:
|
|
118
|
-
blob = Phylax.protect(secret, entropy: app_salt)
|
|
119
|
-
Phylax.unprotect(blob, entropy: app_salt)
|
|
120
|
-
|
|
121
|
-
# Machine scope: any account on this machine can unprotect.
|
|
122
|
-
Phylax.protect(secret, scope: :machine)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Errors
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
StandardError
|
|
129
|
-
└─ Phylax::Error # base for everything phylax raises
|
|
130
|
-
├─ Phylax::AuthenticationError # GCM tag mismatch / tamper, or DPAPI integrity failure
|
|
131
|
-
└─ Phylax::OSError # other Windows API failure (#api, #code)
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
`ArgumentError`/`TypeError` are raised for bad sizes or types *before* any OS call
|
|
135
|
-
(e.g. a key that isn't 32 bytes, an unknown hash, a non-String input).
|
|
136
|
-
|
|
137
|
-
## Security notes
|
|
138
|
-
|
|
139
|
-
- **Algorithms.** AES-256-GCM (128-bit tag), SHA-256/384/512, HMAC-SHA-2,
|
|
140
|
-
PBKDF2-HMAC-SHA-2, `BCryptGenRandom` (SP 800-90A CTR_DRBG). All are the OS's
|
|
141
|
-
validated implementations; phylax only marshals bytes.
|
|
142
|
-
- **Random-nonce limit.** With random 96-bit nonces, keep a single `SecretBox` key
|
|
143
|
-
under ~2³² messages (the GCM birthday bound). Rotate keys for very high volume.
|
|
144
|
-
- **`secure_compare` leaks length.** It returns `false` immediately when lengths
|
|
145
|
-
differ and is constant-time only across equal-length inputs — fine for comparing
|
|
146
|
-
fixed-length MACs, which is its purpose.
|
|
147
|
-
- **DPAPI tamper detection is best-effort.** Windows may return an error *or*, rarely,
|
|
148
|
-
succeed with corrupted output on a mangled blob. If you need a hard integrity
|
|
149
|
-
guarantee, wrap the data in a `SecretBox` (or add your own HMAC) first.
|
|
150
|
-
- **Windows/MSVC only.** The extension links `bcrypt.lib` + `crypt32.lib` and is
|
|
151
|
-
built with `cl.exe` against a native-MSVC Ruby.
|
|
152
|
-
|
|
153
|
-
## License
|
|
154
|
-
|
|
155
|
-
[MIT](LICENSE.txt).
|
|
1
|
+
# phylax (discontinued)
|
|
2
|
+
|
|
3
|
+
**phylax is no longer maintained.** This final release (0.1.1) contains no
|
|
4
|
+
code: installing it compiles nothing, and `require "phylax"` raises a
|
|
5
|
+
`LoadError` explaining the discontinuation.
|
|
6
|
+
|
|
7
|
+
Use Ruby's built-in openssl, lockbox or rbnacl instead.
|
|
8
|
+
|
|
9
|
+
Earlier releases remain on RubyGems only because RubyGems does not allow
|
|
10
|
+
versions older than 30 days to be yanked. They are unmaintained and not
|
|
11
|
+
recommended.
|
data/lib/phylax.rb
CHANGED
|
@@ -1,252 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# phylax — misuse-resistant cryptography for Ruby, backed by the Windows CNG
|
|
7
|
-
# provider (bcrypt.dll) and DPAPI (crypt32.dll).
|
|
8
|
-
#
|
|
9
|
-
# It implements no cryptography itself; it is a thin, hard-to-misuse binding to
|
|
10
|
-
# the operating system's own validated primitives.
|
|
11
|
-
#
|
|
12
|
-
# key = Phylax::SecretBox.generate_key # 32 secure random bytes
|
|
13
|
-
# box = Phylax::SecretBox.new(key)
|
|
14
|
-
# sealed = box.seal("attack at dawn") # fresh nonce per call, authenticated
|
|
15
|
-
# box.open(sealed) # => "attack at dawn"
|
|
16
|
-
#
|
|
17
|
-
# Phylax.sha256("data") # raw 32-byte digest
|
|
18
|
-
# Phylax.hmac_sha256(key, "msg") # keyed MAC
|
|
19
|
-
# Phylax.pbkdf2(password: pw, salt: s, iterations: 600_000, length: 32)
|
|
20
|
-
# Phylax.random_bytes(16)
|
|
21
|
-
# Phylax.protect(secret) # DPAPI, bound to this user
|
|
22
|
-
module Phylax
|
|
23
|
-
# The only hash algorithms the gem accepts, mapped to their C-side index.
|
|
24
|
-
HASHES = { sha256: 0, sha384: 1, sha512: 2 }.freeze
|
|
25
|
-
|
|
26
|
-
# The C bridge methods are internal plumbing; the public, validated API below
|
|
27
|
-
# wraps them. Keep them off the public surface so callers can't skip the
|
|
28
|
-
# keyword/size checks (e.g. a negative PBKDF2 iteration count).
|
|
29
|
-
private_class_method :__hash, :__hmac, :__pbkdf2, :__protect, :__unprotect
|
|
30
|
-
|
|
31
|
-
module_function
|
|
32
|
-
|
|
33
|
-
# --- one-shot hashing ----------------------------------------------------
|
|
34
|
-
|
|
35
|
-
def sha256(data) = __hash(0, data)
|
|
36
|
-
def sha384(data) = __hash(1, data)
|
|
37
|
-
def sha512(data) = __hash(2, data)
|
|
38
|
-
|
|
39
|
-
# --- one-shot HMAC -------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
def hmac_sha256(key, data) = __hmac(0, key, data)
|
|
42
|
-
def hmac_sha384(key, data) = __hmac(1, key, data)
|
|
43
|
-
def hmac_sha512(key, data) = __hmac(2, key, data)
|
|
44
|
-
|
|
45
|
-
# --- PBKDF2 key derivation ----------------------------------------------
|
|
46
|
-
|
|
47
|
-
# Derive a key from a password and salt. Defaults to HMAC-SHA256.
|
|
48
|
-
# password: secret bytes (String)
|
|
49
|
-
# salt: non-secret bytes (String); must not be empty, >= 16 advised
|
|
50
|
-
# iterations: PRF iteration count (Integer >= 1)
|
|
51
|
-
# length: desired key length in bytes (Integer >= 1)
|
|
52
|
-
# hash: :sha256 / :sha384 / :sha512
|
|
53
|
-
def pbkdf2(password:, salt:, iterations:, length:, hash: :sha256)
|
|
54
|
-
idx = HASHES.fetch(hash) { raise ArgumentError, "unknown hash #{hash.inspect}" }
|
|
55
|
-
raise ArgumentError, "iterations must be >= 1" if iterations < 1
|
|
56
|
-
raise ArgumentError, "length must be >= 1" if length < 1
|
|
57
|
-
raise ArgumentError, "salt must not be empty" if String(salt).empty?
|
|
58
|
-
|
|
59
|
-
__pbkdf2(idx, password, salt, iterations, length)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# --- DPAPI: protect secrets at rest -------------------------------------
|
|
63
|
-
|
|
64
|
-
# Encrypt +data+ with a key the OS derives from the current user (default)
|
|
65
|
-
# or the machine. The returned opaque blob can only be unprotected on the
|
|
66
|
-
# same machine (and, for :user scope, by the same user). An optional
|
|
67
|
-
# +entropy+ string acts as a second secret that must be supplied identically
|
|
68
|
-
# to #unprotect.
|
|
69
|
-
def protect(data, scope: :user, entropy: nil)
|
|
70
|
-
__protect(data, machine_scope!(scope), entropy)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Reverse of #protect. Raises Phylax::AuthenticationError if the blob was
|
|
74
|
-
# tampered with, the wrong entropy was given, or it belongs to another
|
|
75
|
-
# user/machine. (DPAPI infers the scope from the blob; +scope+ is validated
|
|
76
|
-
# for symmetry but does not change the operation.)
|
|
77
|
-
def unprotect(data, scope: :user, entropy: nil)
|
|
78
|
-
machine_scope!(scope)
|
|
79
|
-
__unprotect(data, entropy)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def machine_scope!(scope)
|
|
83
|
-
case scope
|
|
84
|
-
when :user then false
|
|
85
|
-
when :machine then true
|
|
86
|
-
else raise ArgumentError, "scope must be :user or :machine, got #{scope.inspect}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
private_class_method :machine_scope!
|
|
90
|
-
|
|
91
|
-
# Base class for every error phylax raises.
|
|
92
|
-
class Error < StandardError; end
|
|
93
|
-
|
|
94
|
-
# The data could not be authenticated: a SecretBox ciphertext (or its AAD)
|
|
95
|
-
# was tampered with or decrypted with the wrong key, or a DPAPI blob failed
|
|
96
|
-
# its integrity/credential check. Never trust any output when this is raised.
|
|
97
|
-
class AuthenticationError < Error; end
|
|
98
|
-
|
|
99
|
-
# A Windows API returned a failure we surface verbatim. #api names the call;
|
|
100
|
-
# #code is the NTSTATUS or Win32 error value.
|
|
101
|
-
class OSError < Error
|
|
102
|
-
def api = @api
|
|
103
|
-
def code = @code
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Streaming message digest (SHA-256/384/512).
|
|
107
|
-
#
|
|
108
|
-
# d = Phylax::Digest.new(:sha256)
|
|
109
|
-
# d << "chunk one" << "chunk two"
|
|
110
|
-
# d.hexdigest # non-destructive; you may keep updating
|
|
111
|
-
class Digest
|
|
112
|
-
# Bytes in the final digest, keyed by algorithm symbol.
|
|
113
|
-
SIZES = { sha256: 32, sha384: 48, sha512: 64 }.freeze
|
|
114
|
-
|
|
115
|
-
attr_reader :name
|
|
116
|
-
|
|
117
|
-
def initialize(name)
|
|
118
|
-
idx = HASHES.fetch(name) { raise ArgumentError, "unknown hash #{name.inspect}" }
|
|
119
|
-
@name = name
|
|
120
|
-
_init(idx)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def <<(data)
|
|
124
|
-
update(data)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Discard any fed data and start over.
|
|
128
|
-
def reset
|
|
129
|
-
_init(HASHES.fetch(@name))
|
|
130
|
-
self
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def hexdigest
|
|
134
|
-
digest.unpack1("H*")
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Convenience: hash +data+ in one call.
|
|
138
|
-
def self.digest(name, data)
|
|
139
|
-
new(name).update(data).digest
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def self.hexdigest(name, data)
|
|
143
|
-
digest(name, data).unpack1("H*")
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def size
|
|
147
|
-
digest_length
|
|
148
|
-
end
|
|
149
|
-
alias length size
|
|
150
|
-
|
|
151
|
-
def inspect
|
|
152
|
-
"#<Phylax::Digest #{@name}>"
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
private :_init
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Streaming keyed MAC (HMAC-SHA-256/384/512).
|
|
159
|
-
#
|
|
160
|
-
# m = Phylax::HMAC.new(:sha256, key)
|
|
161
|
-
# m << "part one" << "part two"
|
|
162
|
-
# m.hexdigest
|
|
163
|
-
class HMAC
|
|
164
|
-
attr_reader :name
|
|
165
|
-
|
|
166
|
-
def initialize(name, key)
|
|
167
|
-
idx = HASHES.fetch(name) { raise ArgumentError, "unknown hash #{name.inspect}" }
|
|
168
|
-
@name = name
|
|
169
|
-
_init(idx, key)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def <<(data)
|
|
173
|
-
update(data)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def hexdigest
|
|
177
|
-
digest.unpack1("H*")
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def size
|
|
181
|
-
digest_length
|
|
182
|
-
end
|
|
183
|
-
alias length size
|
|
184
|
-
|
|
185
|
-
# Compute the MAC of +data+ under +key+ and compare it to +expected_tag+ in
|
|
186
|
-
# constant time. Returns true/false and never raises on mismatch — the safe
|
|
187
|
-
# way to verify a MAC.
|
|
188
|
-
def self.verify(name, key, data, expected_tag)
|
|
189
|
-
tag = new(name, key).update(data).digest
|
|
190
|
-
Phylax.secure_compare(tag, expected_tag)
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def inspect
|
|
194
|
-
"#<Phylax::HMAC #{@name}>"
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
private :_init
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Authenticated symmetric encryption (AES-256-GCM) with a misuse-resistant
|
|
201
|
-
# API: a fresh 96-bit nonce is generated on every #seal and framed into the
|
|
202
|
-
# output, so nonce reuse is impossible by construction, and #open refuses to
|
|
203
|
-
# return anything that fails authentication.
|
|
204
|
-
class SecretBox
|
|
205
|
-
KEY_BYTES = 32
|
|
206
|
-
NONCE_BYTES = 12
|
|
207
|
-
TAG_BYTES = 16
|
|
208
|
-
OVERHEAD = NONCE_BYTES + TAG_BYTES # bytes #seal adds over the plaintext
|
|
209
|
-
|
|
210
|
-
# A new random 32-byte key.
|
|
211
|
-
def self.generate_key
|
|
212
|
-
Phylax.random_bytes(KEY_BYTES)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Derive a box's key from a password via PBKDF2-HMAC-SHA256. +salt+ is
|
|
216
|
-
# required and should be random and stored alongside the ciphertext.
|
|
217
|
-
def self.from_password(password, salt:, iterations: 600_000)
|
|
218
|
-
key = Phylax.pbkdf2(password: password, salt: salt,
|
|
219
|
-
iterations: iterations, length: KEY_BYTES, hash: :sha256)
|
|
220
|
-
new(key)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# +key+ must be exactly 32 bytes (e.g. from .generate_key or .from_password).
|
|
224
|
-
def initialize(key)
|
|
225
|
-
key = String(key)
|
|
226
|
-
unless key.bytesize == KEY_BYTES
|
|
227
|
-
raise ArgumentError, "key must be exactly #{KEY_BYTES} bytes, got #{key.bytesize}"
|
|
228
|
-
end
|
|
229
|
-
_init(key)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Encrypt and authenticate +plaintext+. Optional +aad+ (additional
|
|
233
|
-
# authenticated data) is authenticated but not encrypted, and must be
|
|
234
|
-
# supplied identically to #open. Returns nonce ‖ ciphertext ‖ tag.
|
|
235
|
-
def seal(plaintext, aad: nil)
|
|
236
|
-
_seal(plaintext, aad)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Verify and decrypt a blob from #seal. Raises Phylax::AuthenticationError
|
|
240
|
-
# if the ciphertext, tag, nonce, or aad don't match (or the blob is too
|
|
241
|
-
# short to be authentic).
|
|
242
|
-
def open(sealed, aad: nil)
|
|
243
|
-
_open(sealed, aad)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def inspect
|
|
247
|
-
"#<Phylax::SecretBox>" # never expose key material
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
private :_init, :_seal, :_open
|
|
251
|
-
end
|
|
252
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
raise LoadError, "phylax is discontinued and this release contains no code. Use Ruby's built-in openssl, lockbox or rbnacl instead." \
|
|
4
|
+
" Earlier phylax releases are unmaintained and not recommended."
|
metadata
CHANGED
|
@@ -1,106 +1,30 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phylax
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ned
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '13.0'
|
|
19
|
-
type: :development
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '13.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rake-compiler
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.2'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '1.2'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: minitest
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '5.0'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '5.0'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: vcvars
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.1'
|
|
61
|
-
- - ">="
|
|
62
|
-
- !ruby/object:Gem::Version
|
|
63
|
-
version: 0.1.1
|
|
64
|
-
type: :development
|
|
65
|
-
prerelease: false
|
|
66
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
67
|
-
requirements:
|
|
68
|
-
- - "~>"
|
|
69
|
-
- !ruby/object:Gem::Version
|
|
70
|
-
version: '0.1'
|
|
71
|
-
- - ">="
|
|
72
|
-
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.1.1
|
|
74
|
-
description: |
|
|
75
|
-
phylax (Greek "guardian") is a thin, safe-by-default binding to the Windows
|
|
76
|
-
Cryptography API: Next Generation (CNG / bcrypt.dll) and DPAPI. It does not
|
|
77
|
-
implement any cryptography of its own — it exposes the operating system's own
|
|
78
|
-
validated primitives through an ergonomic, hard-to-misuse Ruby API: a
|
|
79
|
-
cryptographically secure RNG, SHA-2 hashing and HMAC (one-shot and streaming),
|
|
80
|
-
PBKDF2 key derivation, authenticated AES-256-GCM encryption (SecretBox, with
|
|
81
|
-
nonces generated and framed automatically so reuse is impossible), a
|
|
82
|
-
constant-time comparison, and DPAPI protect/unprotect for secrets at rest.
|
|
83
|
-
Windows MSVC (mswin) Ruby only.
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: phylax is discontinued. This final release contains no code and exists
|
|
13
|
+
only so that installing phylax explains the discontinuation instead of building
|
|
14
|
+
an unmaintained native extension.
|
|
84
15
|
executables: []
|
|
85
|
-
extensions:
|
|
86
|
-
- ext/phylax/extconf.rb
|
|
16
|
+
extensions: []
|
|
87
17
|
extra_rdoc_files: []
|
|
88
18
|
files:
|
|
89
|
-
- CHANGELOG.md
|
|
90
19
|
- LICENSE.txt
|
|
91
20
|
- README.md
|
|
92
|
-
- ext/phylax/extconf.rb
|
|
93
|
-
- ext/phylax/phylax.c
|
|
94
21
|
- lib/phylax.rb
|
|
95
|
-
- lib/phylax/version.rb
|
|
96
|
-
homepage: https://github.com/main-path/phylax
|
|
97
22
|
licenses:
|
|
98
23
|
- MIT
|
|
99
24
|
metadata:
|
|
100
|
-
homepage_uri: https://github.com/main-path/phylax
|
|
101
|
-
changelog_uri: https://github.com/main-path/phylax/blob/main/CHANGELOG.md
|
|
102
|
-
bug_tracker_uri: https://github.com/main-path/phylax/issues
|
|
103
25
|
rubygems_mfa_required: 'true'
|
|
26
|
+
post_install_message: phylax is discontinued and this release contains no code. Use
|
|
27
|
+
Ruby's built-in openssl, lockbox or rbnacl instead.
|
|
104
28
|
rdoc_options: []
|
|
105
29
|
require_paths:
|
|
106
30
|
- lib
|
|
@@ -108,7 +32,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
108
32
|
requirements:
|
|
109
33
|
- - ">="
|
|
110
34
|
- !ruby/object:Gem::Version
|
|
111
|
-
version: '
|
|
35
|
+
version: '2.0'
|
|
112
36
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
37
|
requirements:
|
|
114
38
|
- - ">="
|
|
@@ -117,5 +41,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
117
41
|
requirements: []
|
|
118
42
|
rubygems_version: 3.6.9
|
|
119
43
|
specification_version: 4
|
|
120
|
-
summary:
|
|
44
|
+
summary: Discontinued. Use Ruby's built-in openssl, lockbox or rbnacl instead.
|
|
121
45
|
test_files: []
|
data/CHANGELOG.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## [0.1.0] - 2026-06-01
|
|
4
|
-
|
|
5
|
-
Initial release.
|
|
6
|
-
|
|
7
|
-
- `Phylax::SecretBox` — authenticated AES-256-GCM encryption with automatic,
|
|
8
|
-
per-message random nonces framed as `nonce ‖ ciphertext ‖ tag`; nonce reuse is
|
|
9
|
-
impossible through the public API. `seal`/`open` with optional AAD,
|
|
10
|
-
`generate_key`, and `from_password` (PBKDF2-HMAC-SHA256).
|
|
11
|
-
- `Phylax.sha256/sha384/sha512` and streaming `Phylax::Digest` (non-destructive
|
|
12
|
-
`#digest` via `BCryptDuplicateHash`).
|
|
13
|
-
- `Phylax.hmac_sha256/sha384/sha512`, streaming `Phylax::HMAC`, and the
|
|
14
|
-
constant-time `Phylax::HMAC.verify`.
|
|
15
|
-
- `Phylax.pbkdf2` (HMAC-SHA-2 PRF; releases the GVL during derivation).
|
|
16
|
-
- `Phylax.random_bytes` (system-preferred CSPRNG).
|
|
17
|
-
- `Phylax.secure_compare` (constant-time for equal-length inputs).
|
|
18
|
-
- `Phylax.protect` / `Phylax.unprotect` — DPAPI secrets at rest, user or machine
|
|
19
|
-
scope, optional entropy.
|
|
20
|
-
- Error taxonomy: `Phylax::Error`, `Phylax::AuthenticationError`, `Phylax::OSError`.
|
|
21
|
-
|
|
22
|
-
Built on the Windows CNG provider (`bcrypt.dll`) and DPAPI (`crypt32.dll`); no
|
|
23
|
-
cryptography is implemented in the gem itself. Windows MSVC (mswin) Ruby only.
|
data/ext/phylax/extconf.rb
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
#
|
|
3
|
-
# extconf.rb for the phylax extension — ergonomic Ruby bindings to the Windows
|
|
4
|
-
# Cryptography API: Next Generation (CNG / bcrypt.dll) plus DPAPI (crypt32.dll).
|
|
5
|
-
|
|
6
|
-
require "mkmf"
|
|
7
|
-
|
|
8
|
-
unless RbConfig::CONFIG["target_os"] =~ /mswin/
|
|
9
|
-
abort <<~MSG
|
|
10
|
-
phylax requires a native Windows MSVC (mswin) Ruby — it binds the Windows
|
|
11
|
-
CNG primitive provider (bcrypt.dll) and DPAPI (crypt32.dll) and is built
|
|
12
|
-
with cl.exe. Your Ruby is "#{RbConfig::CONFIG['arch']}".
|
|
13
|
-
MSG
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# CNG primitives (BCrypt*) and DPAPI (CryptProtectData/CryptUnprotectData).
|
|
17
|
-
# Pure C — no -EHsc, so rb_raise/longjmp is the normal, safe error mechanism.
|
|
18
|
-
$libs = [$libs, "bcrypt.lib", "crypt32.lib"].join(" ")
|
|
19
|
-
|
|
20
|
-
create_makefile("phylax/phylax")
|