Last active
September 29, 2021 09:27
-
-
Save mattwildig/e900c6db5a9daaca575c0a5f194a7ba4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rbnacl' | |
# Libsodium provides "raw" ChaCha20, but it’s not in RbNaCl. We can work round that with | |
# this little class. | |
class ChaCha20Stream | |
extend RbNaCl::Sodium | |
sodium_function :get_stream, | |
:crypto_stream_chacha20, | |
%i[pointer ulong_long pointer pointer] | |
# Provides the first length bytes of the keystream for the given key and nonce. | |
def self.keystream(key, nonce, length) | |
stream = RbNaCl::Util.zeros(length) | |
stream_len = length | |
get_stream(stream, stream_len, nonce, key) | |
stream | |
end | |
end | |
# Utility for displaying raw data nicely. | |
def to_hex(data) | |
data.unpack('H*')[0] | |
end | |
# Bytewise XOR of the two strings, which must be the same length. | |
def xor(data, keystream) | |
data.bytes.zip(keystream.bytes).map {|a,b| ((a ^ b) & 0xff).chr}.join | |
end | |
# The length of buffers for the MAC are formatted as 8 bytes little endian. | |
def formatted_length(data) | |
[data.length].pack('Q<') | |
end | |
# In the IETF variant associated data and cipher text are both zero padded to | |
# a multiple of 16 bytes. | |
def pad16(data) | |
return data if data.length % 16 == 0 | |
padding_len = 16 - data.length % 16 | |
data + "\x00" * padding_len | |
end | |
# Data from the question. | |
nonce = ['0000000000000001'].pack('H*') # 8 bytes | |
key = ['b78b94bdf407e2fb0c4cb01e74fee7db743d4d5ab636fe4c181511137dedfc46'].pack('H*') | |
plain_text = ['0000000000000001'].pack('H*') | |
associated_data = "" | |
# First 32 bytes of the keystream are used as the auth key, the next 32 bytes | |
# are thrown away, and the rest is used for the encryption. | |
keystream = ChaCha20Stream.keystream(key, nonce, 64 + plain_text.length) | |
auth_key = keystream[0...32] | |
encryption_stream = keystream[64..-1] | |
# RbNaCl does provide "raw" access to Poly1305. | |
auth = RbNaCl::OneTimeAuths::Poly1305.new(auth_key) | |
# XOR the plaintext to the cipher stream after the 64th byte. | |
ciphertext = xor(plain_text, encryption_stream) | |
puts "Ciphertext: #{to_hex(ciphertext)}" | |
# The legacy variant calculates the authentication tag over | |
# | |
#. AD | len(AD) | CT | len(CT) | |
# | |
# (AD = associated data, CT = ciphertext). | |
legacy_data = associated_data + formatted_length(associated_data) + ciphertext + formatted_length(ciphertext) | |
legacy_tag = auth.auth(legacy_data) | |
puts "Legacy tag: #{to_hex(legacy_tag)}" | |
# The IETF variant calculates the authentication tag over | |
# | |
#. pad(AD) | pad(CT) | len(AD) | len(CT) | |
# | |
# Note both the padding and the order compared to the original variant. | |
ietf_data = pad16(associated_data) + pad16(ciphertext) + formatted_length(associated_data) + formatted_length(ciphertext) | |
ietf_tag = auth.auth(ietf_data) | |
puts "IETF tag: #{to_hex(ietf_tag)}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Ciphertext: 78260b2aca088071 | |
Legacy tag: 3c8eea6f05b671ed72f1bc61fee7cc22 | |
IETF tag: 4d888c3b8fe1a4ab8a28d5e593fe7a25 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rbnacl' | |
key = ['b78b94bdf407e2fb0c4cb01e74fee7db743d4d5ab636fe4c181511137dedfc46'].pack('H*') | |
nonce_legacy = ['0000000000000001'].pack('H*') # 8 bytes | |
nonce_ietf = ['000000000000000000000001'].pack('H*') # 12 bytes | |
plain_text = ['0000000000000001'].pack('H*') | |
associated_data = "" | |
legacy = RbNaCl::AEAD::ChaCha20Poly1305Legacy.new(key) | |
ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key) | |
ciphertext_legacy = legacy.encrypt(nonce_legacy, plain_text, associated_data) | |
ciphertext_ietf = ietf.encrypt(nonce_ietf, plain_text, associated_data) | |
puts "legacy: #{ciphertext_legacy[-16..-1].unpack('H*')[0]} #{ciphertext_legacy[0...-16].unpack('H*')[0]}" | |
puts "ietf: #{ciphertext_ietf[-16..-1].unpack('H*')[0]} #{ciphertext_ietf[0...-16].unpack('H*')[0]}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
legacy: 3c8eea6f05b671ed72f1bc61fee7cc22 78260b2aca088071 | |
ietf: 4d888c3b8fe1a4ab8a28d5e593fe7a25 78260b2aca088071 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot man! 👍