131 lines
4.4 KiB
Crystal
131 lines
4.4 KiB
Crystal
require "../lib_sodium"
|
|
require "../key"
|
|
require "./public_key"
|
|
require "../crypto_box"
|
|
|
|
class Sodium::CryptoBox
|
|
# You may either send encrypted signed messages using "Authenticated encryption" or encrypt unsigned messages using "Sealed Boxes".
|
|
#
|
|
# For signing without encryption see `Sodium::Sign::SecretKey`.
|
|
#
|
|
# # Authenticated encryption
|
|
# [https://libsodium.gitbook.io/doc/public-key_cryptography/authenticated_encryption](https://libsodium.gitbook.io/doc/public-key_cryptography/authenticated_encryption#purpose)
|
|
# ```
|
|
# bob = Sodium::CryptoBox::SecretKey.new
|
|
# alice = Sodium::CryptoBox::SecretKey.new
|
|
# message = "hi"
|
|
#
|
|
# # Encrypt and sign a message from bob to alice's public_key
|
|
# bob.box alice.public_key do |box|
|
|
# ciphertext = box.encrypt message
|
|
# end
|
|
# ```
|
|
#
|
|
# # Sealed Boxes
|
|
# [https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes#purpose)
|
|
# ```
|
|
# secret_key = Sodium::CryptoBox::SecretKey.new
|
|
# public_key = secret_key.public_key
|
|
#
|
|
# ciphertext = public_key.encrypt message
|
|
# secret_key.decrypt ciphertext
|
|
# ```
|
|
class SecretKey < Key
|
|
KEY_SIZE = LibSodium.crypto_box_secretkeybytes.to_i
|
|
SEED_SIZE = LibSodium.crypto_box_seedbytes.to_i
|
|
SEAL_SIZE = LibSodium.crypto_box_sealbytes.to_i
|
|
|
|
getter public_key : PublicKey
|
|
|
|
# Returns key
|
|
delegate to_slice, to: @sbuf
|
|
|
|
@seed : SecureBuffer?
|
|
|
|
# Generate a new random secret/public key pair.
|
|
def initialize
|
|
@sbuf = SecureBuffer.new KEY_SIZE
|
|
@public_key = PublicKey.new
|
|
if LibSodium.crypto_box_keypair(@public_key.to_slice, self.to_slice) != 0
|
|
raise Sodium::Error.new("crypto_box_keypair")
|
|
end
|
|
end
|
|
|
|
# Use existing secret and public keys.
|
|
#
|
|
# Copies secret key to a SecureBuffer.
|
|
# Recomputes the public key from a secret key if missing.
|
|
def initialize(bytes : Bytes, pkey : Bytes? = nil)
|
|
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") if bytes.bytesize != KEY_SIZE
|
|
@sbuf = SecureBuffer.new bytes
|
|
if pk = pkey
|
|
@public_key = PublicKey.new pk
|
|
else
|
|
@public_key = PublicKey.new
|
|
if LibSodium.crypto_scalarmult_base(@public_key.to_slice, self.to_slice) != 0
|
|
raise Sodium::Error.new("crypto_scalarmult_base")
|
|
end
|
|
end
|
|
end
|
|
|
|
# Derive a new secret/public key pair based on a consistent seed.
|
|
#
|
|
# Copies seed to a SecureBuffer.
|
|
def initialize(*, seed : Bytes, erase = false)
|
|
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
|
@seed = SecureBuffer.new seed, erase: erase
|
|
|
|
@sbuf = SecureBuffer.new KEY_SIZE
|
|
@public_key = PublicKey.new
|
|
if LibSodium.crypto_box_seed_keypair(@public_key.to_slice, self.to_slice, seed) != 0
|
|
raise Sodium::Error.new("crypto_box_seed_keypair")
|
|
end
|
|
end
|
|
|
|
# Derive a new secret/public key pair based on a consistent seed.
|
|
def initialize(*, seed : SecureBuffer)
|
|
raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
|
|
@seed = seed
|
|
|
|
@sbuf = SecureBuffer.new KEY_SIZE
|
|
@public_key = PublicKey.new
|
|
if LibSodium.crypto_box_seed_keypair(@public_key.to_slice, self.to_slice, seed) != 0
|
|
raise Sodium::Error.new("crypto_box_seed_keypair")
|
|
end
|
|
end
|
|
|
|
def seed
|
|
# BUG: Generate seed if not set.
|
|
@seed.not_nil!.to_slice
|
|
end
|
|
|
|
# Return a Box containing a precomputed shared secret for use with authenticated encryption/decryption.
|
|
def box(public_key) : CryptoBox
|
|
CryptoBox.new self, public_key
|
|
end
|
|
|
|
# Create a new box and automatically close when the block exits.
|
|
def box(public_key)
|
|
b = box public_key
|
|
begin
|
|
yield b
|
|
ensure
|
|
b.close
|
|
end
|
|
end
|
|
|
|
# Anonymously receive messages without a signature.
|
|
#
|
|
# For authenticated messages use `secret_key.box(recipient_public_key).decrypt`.
|
|
def decrypt(src)
|
|
encrypt src.to_slice
|
|
end
|
|
|
|
def decrypt(src : Bytes, dst : Bytes = Bytes.new(src.bytesize - SEAL_SIZE)) : Bytes
|
|
if LibSodium.crypto_box_seal_open(dst, src, src.bytesize, @public_key.to_slice, self.to_slice) != 0
|
|
raise Sodium::Error.new("crypto_box_seal_open")
|
|
end
|
|
dst
|
|
end
|
|
end
|
|
end
|