authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-04 12:35:04+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-15 10:04:30+02:00
log5d5711e6fac29faac0731acdc58286c0be5c50b7
tree8cc0d385b1d2a90b160b31bc9540383725b651d5
parent692a774deb45a7326e4783f8ce828865c3559f4b

crypto.ascon: fix incremental processing

In Ascon-AEAD128, we forgot a padding-only block after the AD. And for the hash/XOF moes, we should keep track of the offset between update() and squeeze() calls to make them equivalent to a single-shot call, as in other hash functions/XOFs. Otherwise the construction is not insecure, but the output doesn't match the specification either.

1 files changed, 44 insertions(+), 25 deletions(-)

lib/std/crypto/ascon.zig+44-25
...@@ -386,6 +386,9 @@ pub const AsconAead128 = struct {...@@ -386,6 +386,9 @@ pub const AsconAead128 = struct {
386 self.st.st[0] ^= mem.readInt(u64, &buf, .little);386 self.st.st[0] ^= mem.readInt(u64, &buf, .little);
387 }387 }
388 self.st.permuteR(8);388 self.st.permuteR(8);
389 } else {
390 self.st.st[0] ^= 0x01;
391 self.st.permuteR(8);
389 }392 }
390 }393 }
391394
...@@ -589,6 +592,7 @@ pub const AsconHash256 = struct {...@@ -589,6 +592,7 @@ pub const AsconHash256 = struct {
589 pub const block_length = 8;592 pub const block_length = 8;
590593
591 st: AsconState,594 st: AsconState,
595 offset: usize,
592596
593 pub const Options = struct {};597 pub const Options = struct {};
594598
...@@ -606,7 +610,7 @@ pub const AsconHash256 = struct {...@@ -606,7 +610,7 @@ pub const AsconHash256 = struct {
606 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };610 const words: [5]u64 = .{ iv, 0, 0, 0, 0 };
607 var st = AsconState.initFromWords(words);611 var st = AsconState.initFromWords(words);
608 st.permuteR(12);612 st.permuteR(12);
609 return AsconHash256{ .st = st };613 return AsconHash256{ .st = st, .offset = 0 };
610 }614 }
611615
612 /// Compute Ascon-Hash256 hash of input data in one call.616 /// Compute Ascon-Hash256 hash of input data in one call.
...@@ -630,6 +634,13 @@ pub const AsconHash256 = struct {...@@ -630,6 +634,13 @@ pub const AsconHash256 = struct {
630 pub fn update(self: *AsconHash256, b: []const u8) void {634 pub fn update(self: *AsconHash256, b: []const u8) void {
631 var i: usize = 0;635 var i: usize = 0;
632636
637 // Finish the block left partially absorbed by a previous call
638 while (self.offset > 0 and i < b.len) : (i += 1) {
639 self.st.addByte(b[i], self.offset);
640 self.offset = (self.offset + 1) % block_length;
641 if (self.offset == 0) self.st.permuteR(12);
642 }
643
633 // Process full 64-bit blocks644 // Process full 64-bit blocks
634 while (i + 8 <= b.len) : (i += 8) {645 while (i + 8 <= b.len) : (i += 8) {
635 self.st.addBytes(b[i..][0..8]);646 self.st.addBytes(b[i..][0..8]);
...@@ -638,16 +649,8 @@ pub const AsconHash256 = struct {...@@ -638,16 +649,8 @@ pub const AsconHash256 = struct {
638649
639 // Store partial block for finalization650 // Store partial block for finalization
640 if (i < b.len) {651 if (i < b.len) {
641 var padded: [8]u8 = @splat(0);652 self.st.addBytes(b[i..]);
642 const remaining = b.len - i;653 self.offset = b.len - i;
643 @memcpy(padded[0..remaining], b[i..]);
644 padded[remaining] = 0x01;
645 self.st.addBytes(&padded);
646 } else {
647 // Add padding block
648 var padded: [8]u8 = @splat(0);
649 padded[0] = 0x01;
650 self.st.addBytes(&padded);
651 }654 }
652 }655 }
653656
...@@ -658,6 +661,7 @@ pub const AsconHash256 = struct {...@@ -658,6 +661,7 @@ pub const AsconHash256 = struct {
658 ///661 ///
659 /// Note: After calling final(), the hasher should not be used again662 /// Note: After calling final(), the hasher should not be used again
660 pub fn final(self: *AsconHash256, out: *[digest_length]u8) void {663 pub fn final(self: *AsconHash256, out: *[digest_length]u8) void {
664 self.st.addByte(0x01, self.offset);
661 // Final permutation after padding665 // Final permutation after padding
662 self.st.permuteR(12);666 self.st.permuteR(12);
663667
...@@ -772,19 +776,21 @@ pub const AsconXof128 = struct {...@@ -772,19 +776,21 @@ pub const AsconXof128 = struct {
772 self.st.addBytes(&padded);776 self.st.addBytes(&padded);
773 self.st.permuteR(12);777 self.st.permuteR(12);
774 self.squeezed = true;778 self.squeezed = true;
779 self.buf_len = 0;
775 }780 }
776781
777 var i: usize = 0;782 var i: usize = 0;
778 while (i < out.len) {783 while (i < out.len) {
779 const to_copy = @min(8, out.len - i);784 if (self.buf_len == block_length) {
785 self.st.permuteR(12);
786 self.buf_len = 0;
787 }
788 const to_copy = @min(block_length - self.buf_len, out.len - i);
780 var block: [8]u8 = undefined;789 var block: [8]u8 = undefined;
781 mem.writeInt(u64, &block, self.st.st[0], .little);790 mem.writeInt(u64, &block, self.st.st[0], .little);
782 @memcpy(out[i..][0..to_copy], block[0..to_copy]);791 @memcpy(out[i..][0..to_copy], block[self.buf_len..][0..to_copy]);
783 i += to_copy;792 i += to_copy;
784793 self.buf_len += to_copy;
785 if (i < out.len) {
786 self.st.permuteR(12);
787 }
788 }794 }
789 }795 }
790};796};
...@@ -924,19 +930,21 @@ pub const AsconCxof128 = struct {...@@ -924,19 +930,21 @@ pub const AsconCxof128 = struct {
924 self.st.addBytes(&padded);930 self.st.addBytes(&padded);
925 self.st.permuteR(12);931 self.st.permuteR(12);
926 self.squeezed = true;932 self.squeezed = true;
933 self.buf_len = 0;
927 }934 }
928935
929 var i: usize = 0;936 var i: usize = 0;
930 while (i < out.len) {937 while (i < out.len) {
931 const to_copy = @min(8, out.len - i);938 if (self.buf_len == block_length) {
939 self.st.permuteR(12);
940 self.buf_len = 0;
941 }
942 const to_copy = @min(block_length - self.buf_len, out.len - i);
932 var block: [8]u8 = undefined;943 var block: [8]u8 = undefined;
933 mem.writeInt(u64, &block, self.st.st[0], .little);944 mem.writeInt(u64, &block, self.st.st[0], .little);
934 @memcpy(out[i..][0..to_copy], block[0..to_copy]);945 @memcpy(out[i..][0..to_copy], block[self.buf_len..][0..to_copy]);
935 i += to_copy;946 i += to_copy;
936947 self.buf_len += to_copy;
937 if (i < out.len) {
938 self.st.permuteR(12);
939 }
940 }948 }
941 }949 }
942};950};
...@@ -949,6 +957,11 @@ test "Ascon-Hash256 basic test" {...@@ -949,6 +957,11 @@ test "Ascon-Hash256 basic test" {
949957
950 // Verify hash is generated (exact value depends on test vectors)958 // Verify hash is generated (exact value depends on test vectors)
951 try testing.expect(hash.len == 32);959 try testing.expect(hash.len == 32);
960
961 var h = AsconHash256.init(.{});
962 h.update(message[0..5]);
963 h.update(message[5..]);
964 try testing.expectEqualSlices(u8, &hash, &h.finalResult());
952}965}
953966
954test "Ascon-XOF128 basic test" {967test "Ascon-XOF128 basic test" {
...@@ -1122,6 +1135,10 @@ test "Ascon-AEAD128 official test vectors" {...@@ -1122,6 +1135,10 @@ test "Ascon-AEAD128 official test vectors" {
1122 var expected_tag: [16]u8 = undefined;1135 var expected_tag: [16]u8 = undefined;
1123 _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;1136 _ = std.fmt.hexToBytes(&expected_tag, "CCCB674FE18A09A285D6AB11B35675C0") catch unreachable;
1124 try testing.expectEqualSlices(u8, &expected_tag, &tag);1137 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1138
1139 AsconAead128.encrypt(&ciphertext, &tag, plaintext, "0123456789:;<=>?", nonce, key);
1140 _ = std.fmt.hexToBytes(&expected_tag, "E4230CDB8330EE9DC0CFD7C7B346E6DC") catch unreachable;
1141 try testing.expectEqualSlices(u8, &expected_tag, &tag);
1125 }1142 }
11261143
1127 // Test vector 34: Single byte plaintext 0x201144 // Test vector 34: Single byte plaintext 0x20
...@@ -1297,7 +1314,8 @@ test "Ascon-XOF128/CXOF128 streaming chunking invariance" {...@@ -1297,7 +1314,8 @@ test "Ascon-XOF128/CXOF128 streaming chunking invariance" {
1297 var xof2 = AsconXof128.init(.{});1314 var xof2 = AsconXof128.init(.{});
1298 xof2.update("Hello, ");1315 xof2.update("Hello, ");
1299 xof2.update("World!");1316 xof2.update("World!");
1300 xof2.squeeze(&out2);1317 xof2.squeeze(out2[0..3]);
1318 xof2.squeeze(out2[3..]);
1301 try testing.expectEqualSlices(u8, &out1, &out2);1319 try testing.expectEqualSlices(u8, &out1, &out2);
13021320
1303 // CXOF128: one-shot vs split must match1321 // CXOF128: one-shot vs split must match
...@@ -1309,7 +1327,8 @@ test "Ascon-XOF128/CXOF128 streaming chunking invariance" {...@@ -1309,7 +1327,8 @@ test "Ascon-XOF128/CXOF128 streaming chunking invariance" {
1309 var cxof2 = AsconCxof128.init(.{ .custom = "cust" });1327 var cxof2 = AsconCxof128.init(.{ .custom = "cust" });
1310 cxof2.update("Hello, ");1328 cxof2.update("Hello, ");
1311 cxof2.update("World!");1329 cxof2.update("World!");
1312 cxof2.squeeze(&cout2);1330 cxof2.squeeze(cout2[0..8]);
1331 cxof2.squeeze(cout2[8..]);
1313 try testing.expectEqualSlices(u8, &cout1, &cout2);1332 try testing.expectEqualSlices(u8, &cout1, &cout2);
1314}1333}
13151334