3ncr.org is a standard for string encryption / decryption (algorithms + storage format), originally intended for encrypting tokens in configuration files but usable for any UTF-8 string. v1 uses AES-256-GCM for authenticated encryption with a 12-byte random IV:
3ncr.org/1#<base64(iv[12] || ciphertext || tag[16])>
Encrypted values look like
3ncr.org/1#pHRufQld0SajqjHx+FmLMcORfNQi1d674ziOPpG52hqW5+0zfJD91hjXsBsvULVtB017mEghGy3Ohj+GgQY5MQ.
This is the official Ruby implementation.
Add to your Gemfile:
gem "tokencrypt"Or install directly:
gem install tokencryptRequires Ruby 3.1+.
Pick a constructor based on the entropy of your secret — see the 3ncr.org v1 KDF guidance for the canonical recommendation.
If you already have a 32-byte AES-256 key, skip the KDF and pass it directly.
require "securerandom"
require "tokencrypt"
key = SecureRandom.bytes(32) # or load from an env variable / secret store
tc = Tokencrypt::TokenCrypt.from_raw_key(key)For a high-entropy secret that is not already 32 bytes (e.g. a random API token), hash it through SHA3-256:
tc = Tokencrypt::TokenCrypt.from_sha3("some-high-entropy-api-token")For passwords or passphrases, use Tokencrypt::TokenCrypt.from_argon2id. It
uses the parameters recommended by the
3ncr.org v1 spec (m=19456 KiB, t=2, p=1). The salt
must be at least 16 bytes.
require "tokencrypt"
tc = Tokencrypt::TokenCrypt.from_argon2id(
"correct horse battery staple",
"0123456789abcdef"
)This library does not implement the legacy PBKDF2-SHA3 KDF that earlier 3ncr.org
libraries (Go, Node.js, PHP) shipped for backward compatibility. If you need to
decrypt data produced by that KDF, derive the 32-byte key with
OpenSSL::KDF.pbkdf2_hmac using digest: OpenSSL::Digest.new("SHA3-256")
yourself and pass the result to from_raw_key:
require "openssl"
require "tokencrypt"
key = OpenSSL::KDF.pbkdf2_hmac(
secret,
salt: salt,
iterations: iterations,
length: 32,
hash: OpenSSL::Digest.new("SHA3-256")
)
tc = Tokencrypt::TokenCrypt.from_raw_key(key)plaintext = "08019215-B205-4416-B2FB-132962F9952F"
encrypted = tc.encrypt_3ncr(plaintext)
# e.g. "3ncr.org/1#pHRu..."
tc.decrypt_if_3ncr(encrypted) # => plaintextdecrypt_if_3ncr returns the input unchanged when it does not start with the
3ncr.org/1# header. This makes it safe to route every configuration value
through it regardless of whether it was encrypted.
Decryption failures (bad tag, truncated input, malformed base64) raise
Tokencrypt::Error.
This implementation decrypts the canonical v1 envelope test vectors shared with
the Go,
Node.js,
PHP,
Python,
Rust,
Java, and
C# reference libraries. The
32-byte AES key behind those vectors was originally derived via PBKDF2-SHA3-256
with secret = "a", salt = "b", iterations = 1000; the tests hardcode the
resulting key and verify the AES-256-GCM envelope round-trips exactly. See
test/test_tokencrypt.rb.
bundle install
bundle exec rake testMIT — see LICENSE.