Skip to content

Commit

Permalink
generate key pair
Browse files Browse the repository at this point in the history
  • Loading branch information
jshawl committed Feb 6, 2024
1 parent 0f363f1 commit ea2210e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 20 deletions.
1 change: 1 addition & 0 deletions lib/minisign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
require 'minisign/public_key'
require 'minisign/signature'
require 'minisign/private_key'
require 'minisign/key_pair'
37 changes: 37 additions & 0 deletions lib/minisign/key_pair.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Minisign
class KeyPair
include Minisign::Utils

attr_reader :private_key
def initialize(password = nil)
key_id = SecureRandom.bytes(8)
signing_key = Ed25519::SigningKey.generate

key_data = "Ed#{key_id}#{signing_key.to_bytes}#{signing_key.verify_key.to_bytes}"
@checksum = blake2b256(key_data)
@keynum_sk = "#{key_id}#{signing_key.to_bytes}#{signing_key.verify_key.to_bytes}#{@checksum}"

@kdf_opslimit = [0, 0, 0, 2, 0, 0, 0, 0].pack('C*')
@kdf_memlimit = [0, 0, 0, 64, 0, 0, 0, 0].pack('C*')
@kdf_salt = SecureRandom.bytes(32)
if password
p @kdf_opslimit.unpack('N*').sum
kdf_output = derive_key(
password,
@kdf_salt,
[0, 0, 0, 2, 0, 0, 0, 0].pack("V*").unpack('N*').sum,
[0, 0, 0, 64, 0, 0, 0, 0].pack("V*").unpack('N*').sum
)
p "kdf_output #{kdf_output}"
@keynum_sk = xor(kdf_output, @keynum_sk.bytes).pack("C*")
end
@kdf_algorithm = password.nil? ? [0, 0].pack('U*') : 'Sc'
@private_key = Minisign::PrivateKey.new(
Base64.strict_encode64("Ed#{@kdf_algorithm}B2#{@kdf_salt}#{@kdf_opslimit}#{@kdf_memlimit}#{@keynum_sk}"),
password
)
end
end
end
21 changes: 1 addition & 20 deletions lib/minisign/private_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def initialize(str, password = nil)
@kdf_opslimit = bytes[38..45].pack('V*').unpack('N*').sum
@kdf_memlimit = bytes[46..53].pack('V*').unpack('N*').sum
key_data_bytes = if password
kdf_output = derive_key(password, @kdf_salt, @kdf_opslimit, @kdf_memlimit)
kdf_output = derive_key(password, @kdf_salt.pack('C*'), @kdf_opslimit, @kdf_memlimit)
xor(kdf_output, bytes[54..157])
else
bytes[54..157]
Expand All @@ -47,27 +47,8 @@ def key_data(bytes)
[bytes[0..7], bytes[8..39], bytes[40..71], bytes[72..103]]
end

# @return [String] the <kdf_output> used to xor the ed25519 keys
def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
RbNaCl::PasswordHash.scrypt(
password,
kdf_salt.pack('C*'),
kdf_opslimit,
kdf_memlimit,
104
).bytes
end

# rubocop:disable Layout/LineLength

# @return [Array<32 bit unsigned ints>] the byte array containing the key id, the secret and public ed25519 keys, and the checksum
def xor(kdf_output, contents)
# rubocop:enable Layout/LineLength
kdf_output.each_with_index.map do |b, i|
contents[i] ^ b
end
end

# @return [Ed25519::SigningKey] the ed25519 signing key
def ed25519_signing_key
Ed25519::SigningKey.new(@secret_key.pack('C*'))
Expand Down
19 changes: 19 additions & 0 deletions lib/minisign/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,24 @@ def blake2b256(message)
def blake2b512(message)
RbNaCl::Hash::Blake2b.digest(message, { digest_size: 64 })
end

# @return [Array<32 bit unsigned ints>] the byte array containing the key id, the secret and public ed25519 keys, and the checksum
def xor(kdf_output, contents)
# rubocop:enable Layout/LineLength
kdf_output.each_with_index.map do |b, i|
contents[i] ^ b
end
end

# @return [String] the <kdf_output> used to xor the ed25519 keys
def derive_key(password, kdf_salt, kdf_opslimit, kdf_memlimit)
RbNaCl::PasswordHash.scrypt(
password,
kdf_salt,
kdf_opslimit,
kdf_memlimit,
104
).bytes
end
end
end
12 changes: 12 additions & 0 deletions spec/minisign/key_pair_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

describe Minisign::KeyPair do
it 'generates a keypair without a password' do
keypair = Minisign::KeyPair.new
expect(keypair.private_key).to be_truthy
end
it 'generates a keypair with a password' do
keypair = Minisign::KeyPair.new("secret password")
expect(keypair.private_key).to be_truthy
end
end
5 changes: 5 additions & 0 deletions spec/minisign/private_key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
expect(@private_key.kdf_algorithm).to eq('Sc')
end

it 'parses the kdf_algorithm' do
@unencrypted_private_key = Minisign::PrivateKey.new(File.read('test/unencrypted.key'))
expect(@unencrypted_private_key.kdf_algorithm.unpack('C*')).to eq([0, 0])
end

it 'raises if the private key requires a password but is not supplied' do
expect do
Minisign::PrivateKey.new(File.read('test/minisign.key'))
Expand Down

0 comments on commit ea2210e

Please sign in to comment.