From 751c1e738d970e15849accc74b761deef2865c66 Mon Sep 17 00:00:00 2001 From: Bastian Schelter Date: Fri, 24 Oct 2025 16:13:02 +0200 Subject: [PATCH 1/2] Fix signature length bug with UTF-8 encoded messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When signing UTF-8 encoded messages containing multi-byte characters (like German umlauts ö, ü, ä), the signature could be incorrectly sized (e.g., 90 bytes instead of 64 bytes). This occurred because Ruby's string slicing [0, n] operates on characters (not bytes) when the string has UTF-8 encoding. The issue affected both signing and verification: 1. SigningKey#sign: The line `sign_attached(message)[0, signature_bytes]` would slice the first 64 characters instead of the first 64 bytes, resulting in signatures larger than 64 bytes when the combined signature+message buffer inherited UTF-8 encoding from the message. 2. VerifyKey#verify: The concatenation `signature + message` would fail with an encoding compatibility error when trying to combine an ASCII-8BIT signature with a UTF-8 message. Fix: Force messages to binary encoding (ASCII-8BIT) before passing them to the underlying libsodium functions. This ensures: - String slicing operates on bytes, not characters - Signatures are always exactly 64 bytes - No encoding compatibility errors during verification Added test case with German umlauts to prevent regression. --- lib/rbnacl/signatures/ed25519/signing_key.rb | 6 +++++- lib/rbnacl/signatures/ed25519/verify_key.rb | 5 ++++- spec/rbnacl/signatures/ed25519/signing_key_spec.rb | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/rbnacl/signatures/ed25519/signing_key.rb b/lib/rbnacl/signatures/ed25519/signing_key.rb index bc2275d..6693ca0 100644 --- a/lib/rbnacl/signatures/ed25519/signing_key.rb +++ b/lib/rbnacl/signatures/ed25519/signing_key.rb @@ -77,7 +77,11 @@ def initialize(seed) # # @return [String] Signature as bytes def sign(message) - sign_attached(message)[0, signature_bytes] + # Force message to binary encoding to ensure [0, signature_bytes] slices by bytes not characters + # This prevents issues with UTF-8 encoded strings where multi-byte characters would cause + # incorrect signature length (e.g., 90 bytes instead of 64) + message_binary = message.b + sign_attached(message_binary)[0, signature_bytes] end # Sign a message using this key, attaching the signature to the message diff --git a/lib/rbnacl/signatures/ed25519/verify_key.rb b/lib/rbnacl/signatures/ed25519/verify_key.rb index 8282c1a..f7a5055 100644 --- a/lib/rbnacl/signatures/ed25519/verify_key.rb +++ b/lib/rbnacl/signatures/ed25519/verify_key.rb @@ -51,7 +51,10 @@ def initialize(key) def verify(signature, message) signature = signature.to_str Util.check_length(signature, signature_bytes, "signature") - verify_attached(signature + message) + # Force message to binary encoding to prevent encoding compatibility errors + # when concatenating with signature (which is always ASCII-8BIT) + message_binary = message.b + verify_attached(signature + message_binary) end # Verify a signature for a given signed message diff --git a/spec/rbnacl/signatures/ed25519/signing_key_spec.rb b/spec/rbnacl/signatures/ed25519/signing_key_spec.rb index ab54e7e..c2cdfa0 100644 --- a/spec/rbnacl/signatures/ed25519/signing_key_spec.rb +++ b/spec/rbnacl/signatures/ed25519/signing_key_spec.rb @@ -18,6 +18,13 @@ expect(subject.sign(message)).to eq signature end + it "signs UTF-8 encoded messages" do + utf8_message = "Björn Müller from München".dup.force_encoding('UTF-8') + signature = subject.sign(utf8_message) + expect(signature.bytesize).to eq(64) + expect(subject.verify_key.verify(signature, utf8_message)).to be true + end + it "signs messages, full version" do expect(subject.sign_attached(message)[0, RbNaCl::SigningKey.signature_bytes]).to eq signature expect(subject.sign_attached(message)[RbNaCl::SigningKey.signature_bytes, message.length]).to eq message From f3b31aa2bd37c18997529633796131d1161c9144 Mon Sep 17 00:00:00 2001 From: Bastian Schelter Date: Fri, 24 Oct 2025 16:25:27 +0200 Subject: [PATCH 2/2] Fix rubocop complaining --- spec/rbnacl/signatures/ed25519/signing_key_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rbnacl/signatures/ed25519/signing_key_spec.rb b/spec/rbnacl/signatures/ed25519/signing_key_spec.rb index c2cdfa0..cc6d330 100644 --- a/spec/rbnacl/signatures/ed25519/signing_key_spec.rb +++ b/spec/rbnacl/signatures/ed25519/signing_key_spec.rb @@ -19,7 +19,7 @@ end it "signs UTF-8 encoded messages" do - utf8_message = "Björn Müller from München".dup.force_encoding('UTF-8') + utf8_message = "Björn Müller from München".dup.force_encoding("UTF-8") signature = subject.sign(utf8_message) expect(signature.bytesize).to eq(64) expect(subject.verify_key.verify(signature, utf8_message)).to be true