Sodum::CryptoBox::SecretKey may derive keys from a seed.

Sodum::Sign::SecretKey may derive keys from a seed.
Sodum::CryptoBox::Pair renamed to Sodum::CryptoBox::Box
This commit is contained in:
Didactic Drunk 2019-06-29 12:44:47 -07:00
parent 2f4d9ddb6b
commit 41a55a9593
11 changed files with 155 additions and 40 deletions

View File

@ -144,14 +144,14 @@ alice = Sodium::CryptoBox::SecretKey.new
bob = Sodium::CryptoBox::SecretKey.new bob = Sodium::CryptoBox::SecretKey.new
# Precompute a shared secret between alice and bob. # Precompute a shared secret between alice and bob.
pair = alice.pair bob.public_key box = alice.box bob.public_key
# Encrypt a message for Bob using his public key, signing it with Alice's # Encrypt a message for Bob using his public key, signing it with Alice's
# secret key # secret key
nonce, encrypted = pair.encrypt data nonce, encrypted = box.encrypt data
# Precompute within a block. The shared secret is wiped when the block exits. # Precompute within a block. The shared secret is wiped when the block exits.
bob.pair alice.public_key do |pair| bob.box alice.public_key do |box|
# Decrypt the message using Bob's secret key, and verify its signature against # Decrypt the message using Bob's secret key, and verify its signature against
# Alice's public key # Alice's public key
decrypted = Sodium.decrypt(encrypted, nonce, alice.public, bob.secret) decrypted = Sodium.decrypt(encrypted, nonce, alice.public, bob.secret)

View File

@ -1,6 +1,25 @@
require "../../spec_helper" require "../../spec_helper"
private def new_key_bytes
Sodium::CryptoBox::SecretKey.new.bytes
end
describe Sodium::CryptoBox::SecretKey do describe Sodium::CryptoBox::SecretKey do
it "loads keys" do
key1 = Sodium::CryptoBox::SecretKey.new
key2 = Sodium::CryptoBox::SecretKey.new key1.bytes, key1.public_key.bytes
key1.bytes.should eq key2.bytes
key1.public_key.bytes.should eq key2.public_key.bytes
end
it "seed keys" do
seed = Bytes.new Sodium::CryptoBox::SecretKey::SEED_SIZE
key1 = Sodium::CryptoBox::SecretKey.new seed: seed
key2 = Sodium::CryptoBox::SecretKey.new seed: seed
key1.bytes.should eq key2.bytes
key1.public_key.bytes.should eq key2.public_key.bytes
end
it "easy encrypt/decrypt" do it "easy encrypt/decrypt" do
data = "Hello World!" data = "Hello World!"
@ -12,15 +31,19 @@ describe Sodium::CryptoBox::SecretKey do
# Encrypt a message for Bob using his public key, signing it with Alice's # Encrypt a message for Bob using his public key, signing it with Alice's
# secret key # secret key
pair = alice.pair bob.public_key box = alice.box bob.public_key
nonce, encrypted = pair.encrypt_easy data nonce, encrypted = box.encrypt_easy data
# Decrypt the message using Bob's secret key, and verify its signature against # Decrypt the message using Bob's secret key, and verify its signature against
# Alice's public key # Alice's public key
bob.pair alice.public_key do |pair| bob.box alice.public_key do |box|
decrypted = pair.decrypt_easy encrypted, nonce: nonce decrypted = box.decrypt_easy encrypted, nonce: nonce
String.new(decrypted).should eq(data) String.new(decrypted).should eq(data)
end end
end end
it "wipes keys" do
check_wiped new_key_bytes
end
end end

View File

@ -1,7 +1,28 @@
require "../../spec_helper" require "../../spec_helper"
require "../../../src/sodium/sign/secret_key" require "../../../src/sodium/sign/secret_key"
private def new_key_bytes
Sodium::Sign::SecretKey.new.bytes
end
describe Sodium::Sign::SecretKey do describe Sodium::Sign::SecretKey do
it "loads keys" do
key1 = Sodium::Sign::SecretKey.new
key2 = Sodium::Sign::SecretKey.new key1.bytes, key1.public_key.bytes
key1.bytes.should eq key2.bytes
key1.public_key.bytes.should eq key2.public_key.bytes
# TODO: test loading when missing public_key
end
it "seed keys" do
seed = Bytes.new Sodium::Sign::SecretKey::SEED_SIZE
key1 = Sodium::Sign::SecretKey.new seed: seed
key2 = Sodium::Sign::SecretKey.new seed: seed
key1.bytes.should eq key2.bytes
key1.public_key.bytes.should eq key2.public_key.bytes
end
it "signs and verifies" do it "signs and verifies" do
message = "foo" message = "foo"
skey = Sodium::Sign::SecretKey.new skey = Sodium::Sign::SecretKey.new
@ -19,4 +40,8 @@ describe Sodium::Sign::SecretKey do
skey.public_key.verify_detached "bar", sig skey.public_key.verify_detached "bar", sig
end end
end end
it "checks wiped" do
check_wiped new_key_bytes
end
end end

View File

@ -1,2 +1,9 @@
require "spec" require "spec"
require "../src/sodium" require "../src/sodium"
def check_wiped(buf : Bytes)
GC.collect
buf.each do |b|
raise "not wiped #{buf.inspect}" if b != 0_u8
end
end

View File

@ -1,7 +1,7 @@
require "../lib_sodium" require "../lib_sodium"
module Sodium::CryptoBox module Sodium::CryptoBox
class Pair class Box
include Wipe include Wipe
# BUG: precompute size # BUG: precompute size

View File

@ -3,7 +3,7 @@ require "../lib_sodium"
module Sodium::CryptoBox module Sodium::CryptoBox
class PublicKey < Key class PublicKey < Key
include Wipe include Wipe
KEY_SIZE = LibSodium::PUBLIC_KEY_SIZE KEY_SIZE = LibSodium.crypto_box_publickeybytes
getter bytes : Bytes getter bytes : Bytes

View File

@ -3,36 +3,55 @@ require "../lib_sodium"
module Sodium::CryptoBox module Sodium::CryptoBox
class SecretKey < Key class SecretKey < Key
include Wipe include Wipe
KEY_SIZE = LibSodium::SECRET_KEY_SIZE KEY_SIZE = LibSodium.crypto_box_secretkeybytes
SEED_SIZE = LibSodium.crypto_box_seedbytes
MAC_SIZE = LibSodium::MAC_SIZE MAC_SIZE = LibSodium::MAC_SIZE
getter public_key getter public_key
getter bytes : Bytes getter bytes : Bytes
@seed : Bytes?
# Generate a new secret/public key pair. # Generate a new random secret/public key pair.
def initialize def initialize
pkey = Bytes.new(PublicKey::KEY_SIZE) pkey = Bytes.new(PublicKey::KEY_SIZE)
@bytes = Bytes.new(KEY_SIZE) @bytes = Bytes.new(KEY_SIZE)
@public_key = PublicKey.new pkey @public_key = PublicKey.new pkey
LibSodium.crypto_box_keypair(pkey, @bytes) v = LibSodium.crypto_box_keypair(pkey, @bytes)
if v != 0
raise Sodium::Error.new("crypto_box_keypair #{v}")
end
end end
# Use existing Secret and Public keys. # Use existing Secret and Public keys.
def initialize(@bytes : Bytes, pkey : Bytes) def initialize(@bytes : Bytes, pkey : Bytes)
# TODO: finish regenerating public_key
if bytes.bytesize != KEY_SIZE if bytes.bytesize != KEY_SIZE
raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}")
end end
@public_key = PublicKey.new pkey @public_key = PublicKey.new pkey
end end
# Return a Pair containing a precomputed shared secret for use with encryption/decryption. # Derive a new secret/public key pair based on a consistent seed.
def pair(public_key) : Pair def initialize(*, seed : Bytes)
Pair.new self, public_key raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
@seed = seed
pkey = Bytes.new(PublicKey::KEY_SIZE)
@bytes = Bytes.new(KEY_SIZE)
@public_key = PublicKey.new pkey
if LibSodium.crypto_box_seed_keypair(pkey, @bytes, seed) != 0
raise Sodium::Error.new("crypto_box_seed_keypair")
end
end end
# Create a new pair and automatically close when the block exits. # Return a Box containing a precomputed shared secret for use with encryption/decryption.
def pair(public_key) def box(public_key) : Box
pa = pair public_key Box.new self, public_key
end
# Create a new box and automatically close when the block exits.
def box(public_key)
pa = box public_key
begin begin
yield pa yield pa
ensure ensure

View File

@ -5,11 +5,16 @@ module Sodium
fun crypto_box_publickeybytes : LibC::SizeT fun crypto_box_publickeybytes : LibC::SizeT
fun crypto_box_secretkeybytes : LibC::SizeT fun crypto_box_secretkeybytes : LibC::SizeT
fun crypto_box_seedbytes : LibC::SizeT
fun crypto_box_noncebytes : LibC::SizeT fun crypto_box_noncebytes : LibC::SizeT
fun crypto_box_macbytes : LibC::SizeT fun crypto_box_macbytes : LibC::SizeT
fun crypto_sign_publickeybytes : LibC::SizeT fun crypto_sign_publickeybytes : LibC::SizeT
fun crypto_sign_secretkeybytes : LibC::SizeT fun crypto_sign_secretkeybytes : LibC::SizeT
fun crypto_sign_bytes : LibC::SizeT fun crypto_sign_bytes : LibC::SizeT
fun crypto_sign_seedbytes : LibC::SizeT
fun crypto_secretbox_keybytes : LibC::SizeT
fun crypto_secretbox_noncebytes : LibC::SizeT
fun crypto_secretbox_macbytes : LibC::SizeT
fun crypto_kdf_keybytes : LibC::SizeT fun crypto_kdf_keybytes : LibC::SizeT
fun crypto_kdf_contextbytes : LibC::SizeT fun crypto_kdf_contextbytes : LibC::SizeT
fun crypto_pwhash_memlimit_min : LibC::SizeT fun crypto_pwhash_memlimit_min : LibC::SizeT
@ -41,8 +46,6 @@ module Sodium
SECRET_KEY_SIZE = crypto_box_secretkeybytes() SECRET_KEY_SIZE = crypto_box_secretkeybytes()
NONCE_SIZE = crypto_box_noncebytes() NONCE_SIZE = crypto_box_noncebytes()
MAC_SIZE = crypto_box_macbytes() MAC_SIZE = crypto_box_macbytes()
PUBLIC_SIGN_SIZE = crypto_sign_publickeybytes()
SECRET_SIGN_SIZE = crypto_sign_secretkeybytes()
SIGNATURE_SIZE = crypto_sign_bytes() SIGNATURE_SIZE = crypto_sign_bytes()
KDF_KEY_SIZE = crypto_kdf_keybytes() KDF_KEY_SIZE = crypto_kdf_keybytes()
KDF_CONTEXT_SIZE = crypto_kdf_contextbytes() KDF_CONTEXT_SIZE = crypto_kdf_contextbytes()
@ -82,7 +85,13 @@ module Sodium
fun crypto_box_keypair( fun crypto_box_keypair(
public_key_output : Pointer(LibC::UChar), public_key_output : Pointer(LibC::UChar),
secret_key_output : Pointer(LibC::UChar) secret_key_output : Pointer(LibC::UChar)
) ) : LibC::Int
fun crypto_box_seed_keypair(
public_key_output : Pointer(LibC::UChar),
secret_key_output : Pointer(LibC::UChar),
seed : Pointer(LibC::UChar)
) : LibC::Int
fun crypto_box_easy( fun crypto_box_easy(
output : Pointer(LibC::UChar), output : Pointer(LibC::UChar),
@ -107,6 +116,12 @@ module Sodium
secret_key_output : Pointer(LibC::UChar) secret_key_output : Pointer(LibC::UChar)
) : LibC::Int ) : LibC::Int
fun crypto_sign_seed_keypair(
public_key_output : Pointer(LibC::UChar),
secret_key_output : Pointer(LibC::UChar),
seed : Pointer(LibC::UChar)
) : LibC::Int
fun crypto_sign_detached( fun crypto_sign_detached(
signature_output : Pointer(LibC::UChar), signature_output : Pointer(LibC::UChar),
signature_output_size : Pointer(LibC::ULongLong), signature_output_size : Pointer(LibC::ULongLong),
@ -182,4 +197,12 @@ module Sodium
output_len : UInt64 output_len : UInt64
) : LibC::Int ) : LibC::Int
end end
if LibSodium.crypto_secretbox_noncebytes != LibSodium.crypto_box_noncebytes
raise "Assumptions in this library regarding nonce sizes may not be valid"
end
if LibSodium.crypto_secretbox_macbytes != LibSodium.crypto_box_macbytes
raise "Assumptions in this library regarding mac sizes may not be valid"
end
end end

View File

@ -13,7 +13,8 @@ module Sodium
# message = key.decrypt_easy encrypted, nonce # message = key.decrypt_easy encrypted, nonce
# ``` # ```
class SecretBox < Key class SecretBox < Key
KEY_SIZE = LibSodium::SECRET_KEY_SIZE KEY_SIZE = LibSodium.crypto_secretbox_keybytes
NONCE_SIZE = LibSodium.crypto_secretbox_noncebytes
MAC_SIZE = LibSodium::MAC_SIZE MAC_SIZE = LibSodium::MAC_SIZE
property bytes : Bytes property bytes : Bytes

View File

@ -3,7 +3,7 @@ require "../lib_sodium"
module Sodium module Sodium
class Sign::PublicKey < Key class Sign::PublicKey < Key
include Wipe include Wipe
KEY_SIZE = LibSodium::PUBLIC_SIGN_SIZE KEY_SIZE = LibSodium.crypto_sign_publickeybytes
getter bytes : Bytes getter bytes : Bytes
@ -20,7 +20,7 @@ module Sodium
end end
def verify_detached(message : Bytes, sig : Bytes) def verify_detached(message : Bytes, sig : Bytes)
raise ArgumentError.new("Signature must be #{LibSodium::SIGNATURE_SIZE} bytes, got #{sig.bytesize}") raise ArgumentError.new("Signature must be #{LibSodium::SIGNATURE_SIZE} bytes, got #{sig.bytesize}") if sig.bytesize != LibSodium::SIGNATURE_SIZE
v = LibSodium.crypto_sign_verify_detached sig, message, message.bytesize, @bytes v = LibSodium.crypto_sign_verify_detached sig, message, message.bytesize, @bytes
if v != 0 if v != 0

View File

@ -1,36 +1,53 @@
require "../lib_sodium" require "../lib_sodium"
module Sodium module Sodium
# Usage:
# ```
# key = SecretKey.new
# sig = key.sign_detached data
# key.public_key.verify_detached data
# ```
class Sign::SecretKey < Sodium::Key class Sign::SecretKey < Sodium::Key
include Wipe include Wipe
KEY_SIZE = LibSodium::SECRET_SIGN_SIZE KEY_SIZE = LibSodium.crypto_sign_secretkeybytes
SEED_SIZE = LibSodium.crypto_sign_seedbytes
getter bytes : Bytes getter bytes : Bytes
getter public_key getter public_key
@seed : Bytes?
# Generates a new secret/public key pair. # Generates a new random secret/public key pair.
def initialize def initialize
pkey = Bytes.new(Sign::PublicKey::KEY_SIZE) pkey = Bytes.new(Sign::PublicKey::KEY_SIZE)
@bytes = Bytes.new(KEY_SIZE) @bytes = Bytes.new(KEY_SIZE)
@public_key = PublicKey.new pkey @public_key = PublicKey.new pkey
LibSodium.crypto_sign_keypair pkey, @bytes if LibSodium.crypto_sign_keypair(pkey, @bytes) != 0
raise Sodium::Error.new("crypto_sign_keypair")
end
end end
# Use existing Secret and Public keys. # Use existing Secret and Public keys.
def initialize(@bytes : Bytes, pkey : Bytes) def initialize(@bytes : Bytes, pkey : Bytes? = nil)
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}") pkey ||= Bytes.new(Sign::PublicKey::KEY_SIZE).tap do |pk|
# BUG: Finish regenerating public_key
raise "Needs crypto_sign_ed25519_sk_to_pk"
end
raise ArgumentError.new("Secret sign key must be #{KEY_SIZE}, got #{@bytes.bytesize}") unless @bytes.bytesize == KEY_SIZE
@public_key = PublicKey.new pkey @public_key = PublicKey.new pkey
end end
# def initialize(@bytes : Bytes) # Derive a new secret/public key pair based on a consistent seed.
# if bytes.bytesize != KEY_SIZE def initialize(*, seed : Bytes)
# raise ArgumentError.new("Secret key must be #{KEY_SIZE} bytes, got #{bytes.bytesize}") raise ArgumentError.new("Secret sign seed must be #{SEED_SIZE}, got #{seed.bytesize}") unless seed.bytesize == SEED_SIZE
# end @seed = seed
# BUG: fix
# @public_key = PublicKey.new Bytes.new(100) pkey = Bytes.new(Sign::PublicKey::KEY_SIZE)
# raise "Needs crypto_sign_ed25519_sk_to_pk" @bytes = Bytes.new(KEY_SIZE)
# Also needs to differentiate from seed as a single parameter @public_key = PublicKey.new pkey
# end if LibSodium.crypto_sign_seed_keypair(pkey, @bytes, seed) != 0
raise Sodium::Error.new("crypto_sign_seed_keypair")
end
end
# Signs message and returns a detached signature. # Signs message and returns a detached signature.
# Verify using `secret_key.public_key.verify_detached(message, sig)` # Verify using `secret_key.public_key.verify_detached(message, sig)`