Module: LocalVault::ShareCrypto
- Defined in:
- lib/localvault/share_crypto.rb
Overview
Asymmetric encryption for one-time vault sharing (direct share model).
Encrypts a secrets hash for a recipient using their X25519 public key. Uses an ephemeral sender keypair so the sender’s identity key is never transmitted. The recipient decrypts with their private key.
This is used for the localvault share –with @handle flow (one-time handoff). For ongoing team access, see KeySlot.
Defined Under Namespace
Classes: DecryptionError
Class Method Summary collapse
-
.decrypt_from(encrypted_payload_b64, my_private_key_bytes) ⇒ Hash
Decrypt a shared payload using the recipient’s private key.
-
.encrypt_for(secrets, recipient_pub_key_b64) ⇒ String
Encrypt a secrets hash for a recipient using their X25519 public key.
Class Method Details
.decrypt_from(encrypted_payload_b64, my_private_key_bytes) ⇒ Hash
Decrypt a shared payload using the recipient’s private key.
Reverses the envelope produced by encrypt_for, extracting the ephemeral sender public key and using NaCl Box to decrypt.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/localvault/share_crypto.rb', line 57 def self.decrypt_from(encrypted_payload_b64, my_private_key_bytes) raw = Base64.strict_decode64(encrypted_payload_b64) payload = JSON.parse(raw) sender_pub = RbNaCl::PublicKey.new(Base64.strict_decode64(payload.fetch("sender_pub"))) my_sk = RbNaCl::PrivateKey.new(my_private_key_bytes) box = RbNaCl::Box.new(sender_pub, my_sk) nonce = Base64.strict_decode64(payload.fetch("nonce")) ciphertext = Base64.strict_decode64(payload.fetch("ciphertext")) plaintext = box.open(nonce, ciphertext) JSON.parse(plaintext) rescue RbNaCl::CryptoError => e raise DecryptionError, "Failed to decrypt share: #{e.}" rescue JSON::ParserError, KeyError => e raise DecryptionError, "Invalid payload format: #{e.}" rescue ArgumentError => e raise DecryptionError, "Invalid payload encoding: #{e.}" end |
.encrypt_for(secrets, recipient_pub_key_b64) ⇒ String
Encrypt a secrets hash for a recipient using their X25519 public key.
Uses an ephemeral sender keypair (NaCl Box construction) so the sender’s identity private key is never transmitted. The returned blob contains the ephemeral public key, nonce, and ciphertext.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/localvault/share_crypto.rb', line 31 def self.encrypt_for(secrets, recipient_pub_key_b64) recipient_pub = RbNaCl::PublicKey.new(Base64.strict_decode64(recipient_pub_key_b64)) ephemeral_sk = RbNaCl::PrivateKey.generate box = RbNaCl::Box.new(recipient_pub, ephemeral_sk) nonce = RbNaCl::Random.random_bytes(RbNaCl::Box.nonce_bytes) plaintext = JSON.generate(secrets) ciphertext = box.box(nonce, plaintext) payload = { "v" => 1, "sender_pub" => Base64.strict_encode64(ephemeral_sk.public_key.to_bytes), "nonce" => Base64.strict_encode64(nonce), "ciphertext" => Base64.strict_encode64(ciphertext) } Base64.strict_encode64(JSON.generate(payload)) end |