Hi all,

 

Posting before opening a PR per the customary convention.  I have a

working, tested implementation that adds SHA-3 (sha3-256, sha3-384,

sha3-512) to the algorithms accepted by --checksum-choice.

 

I want to lead with the question I expect first, because rsync already

ships a generous set of hashes:

 

   Why add SHA-3 when rsync already has xxh128 / xxh3 / md5 / sha1 /

   sha256 / sha512?

 

Five reasons.  None of them is "SHA-2 is broken" -- it isn't, today.

 

1. Different cryptographic design family.

 

   SHA-2 (sha256, sha512) uses the Merkle-Damgaard construction with

   a Davies-Meyer compression function.  SHA-3 uses the sponge

   construction over the Keccak-f[1600] permutation.  These share no

   structural elements.  A theoretical break in one would not imply a

   break in the other.  Cryptographic agility -- the ability to swap

   to a structurally-independent primitive without redesigning the

   surrounding system -- is a property worth having, especially in

   tools whose output (rsync's --checksum verification, batch files,

   --link-dest farms) can persist for years.

 

2. Length-extension immunity.

 

   SHA-256 and SHA-512 are vulnerable to length-extension attacks:

   given H(s || m), an attacker who does not know s can compute

   H(s || m || pad || m') without knowing s.  rsync does not directly

   use a naive secret-prefix MAC, but operators occasionally do build

   integrity-verification pipelines on top of "rsync checksum + extra

   data".  SHA-3's sponge construction is structurally immune to this

   class of attack; SHA-2 requires HMAC-style wrapping to avoid it.

 

3. FIPS 202 / compliance.

 

   SHA-3 is FIPS 202 (2015).  A growing set of regulated environments

   (FIPS 140-3 module validations, some federal procurement contexts,

   defense-adjacent contracts) explicitly require or prefer SHA-3 for

   new deployments.  Operators in those environments today either

   accept "rsync's strongest hash is SHA-2, that has to be good enough"

   or wrap rsync in external integrity tooling.  Native SHA-3 removes

   the friction.

 

4. No new dependencies.

 

   This is the boring but important one.  SHA-3 has been in OpenSSL's

   EVP since 1.1.1 (released September 2018) -- which means it is in

   every libcrypto rsync already links against on every supported

   distro.  No new vendored crypto code, no autoconf detection, no new

   build dependency.  The implementation is exactly:

 

       { CSUM_SHA3_512, NNI_EVP, "sha3-512", NULL },

       { CSUM_SHA3_384, NNI_EVP, "sha3-384", NULL },

       { CSUM_SHA3_256, NNI_EVP, "sha3-256", NULL },

 

   plus length/canonical case entries.  The existing verify_digest()

   probe handles older libcrypto cleanly: if the linked OpenSSL doesn't

   expose sha3-*, the entry is demoted to CSUM_gone at init and a user

   asking for --checksum-choice=sha3-256 gets the familiar "unknown

   checksum name" error.

 

5. Hardware acceleration is starting to land.

 

   ARMv8.2-A's SHA-3 extension (EOR3, RAX1, XAR, BCAX instructions) is

   shipping in production silicon -- Apple M-series, AWS Graviton 3+,

   recent Snapdragon and MediaTek parts.  On those parts SHA-3 can

   outperform software SHA-2.  Intel/AMD don't have direct SHA-3

   instructions today but OpenSSL's optimized software path is

   competitive.  No advantage on commodity x86 in 2026, but the

   trajectory matters.

 

A few things SHA-3 is NOT being offered for:

 

  - It is not a replacement for xxh3.  xxh3 / xxh128 are

    non-cryptographic and very fast; they are the right default for

    "detect random corruption in a trusted-LAN backup."  SHA-3 is for

    cases where the threat model includes adversarial collision

    construction or compliance.

 

  - It is not being made the default.  The "auto" negotiation path is

    untouched.  An unmodified peer keeps negotiating xxh128 / xxh3 /

    md5 / MD4 as before.  SHA-3 has to be requested explicitly via

    --checksum-choice=sha3-* on both sides (or via the

    RSYNC_CHECKSUM_LIST environment override).

 

  - It is not a protocol bump.  Negotiation in 3.2+ is already

    capability-based via the name_num_obj table; adding entries to

    that table is the entire mechanism that xxh3 / xxh128 use.

 

 

DESIGN

 

Wholly through OpenSSL EVP, no vendored hash code:

 

  - 3 entries in valid_checksums_items[] with NNI_EVP, named

    "sha3-256" / "sha3-384" / "sha3-512" so that EVP_get_digestbyname()

    resolves them directly with no special-case in csum_evp_md().

 

  - 3 new CSUM_SHA3_{256,384,512} constants in lib/md-defines.h, plus

    SHA3_{256,384,512}_DIGEST_LEN literals.  (OpenSSL never published

    SHA3_*_DIGEST_LENGTH macros, so the literal lengths come from us.)

 

  - Length cases in csum_len_for_type(), and -1 in canonical_checksum()

    to match the other strong hashes' byte order.

 

  - No new code paths in get_checksum2(), file_checksum(), or

    sum_init/update/end.  They all short-circuit to the EVP path when

    xfer_sum_evp_md / file_sum_evp_md / cur_sum_evp_md is set, so SHA-3

    just rides the existing rails -- same as sha256 and sha512.

 

 

VERIFIED

 

Built on Ubuntu 22.04 / OpenSSL 3.0.2 / gcc 11.4, against upstream

master at b9cc0c6 (3.4.2 + 12 commits, protocol 32).

 

  * rsync --version "Checksum list" now reads:

        xxh128 xxh3 xxh64 (xxhash) sha3-512 sha3-384 sha3-256

        md5 md4 sha1 none

 

  * FIPS 202 vector match:

        SHA3-256("abc") = 3a985da7...1532  (correct)

 

  * 1 GiB urandom file, --checksum-choice={sha3-256,sha3-384,sha3-512}:

 

    Local copy:        byte-identical, digest matches reference

                       openssl dgst output in all three.

    rsync:// PULL:     same.  (daemon and client both built from this

                       patch, listening on 127.0.0.1:8730.)

    rsync:// PUSH:     same.

    Delta-update path: corrupted 16 x 64 KiB blocks scattered through

                       a 1 GiB destination, resync with sha3-256 and

                       --no-whole-file.  rsync sent 1.5 MiB literal +

                       1.7 MiB metadata, matched 1022 MiB of existing

                       blocks (555x speedup), and the assembled file

                       was byte-identical with matching sha3-256

                       digest.  Confirms the rolling-block strong-

                       checksum path is wired correctly.

 

  * With --debug=NSTR, auto negotiation still picks xxh128 by default;

    SHA-3 is selected only when explicitly requested.

 

  * `make check` passes the new testsuite/checksum-sha3.test.  (The

    pre-existing 'devices' failure on the test host is environmental

    and reproduces on unmodified master -- it needs root to create

    /dev/* nodes.)

 

 

DIFFSTAT

 

  NEWS.md                      | 11 +++++

  checksum.c                   | 20 +++++++++

  lib/md-defines.h             | 12 +++++

  rsync.1.md                   |  7 +++

  testsuite/checksum-sha3.test | 60 ++++++++++++++++++++++++

  5 files changed, 110 insertions(+)

 

 

OPEN QUESTIONS FOR THE LIST

 

  (a) Algorithm names.  I used "sha3-256" / "sha3-384" / "sha3-512"

      to mirror OpenSSL's EVP digest names directly so the lookup is

      transparent.  Happy to switch to "sha3_256" / etc. if the house

      style prefers underscores.

 

  (b) Single commit, or split implementation / docs / test into a

      three-commit series?  I've gone with one commit since the change

      is small and tightly coupled, but I'm easy.

 

  (c) Anything missing on the protocol / negotiation side I should

      worry about?  As far as I can see the name_num_obj negotiation

      handles new entries automatically and no protocol-version bump

      is required, but I'd rather have that confirmed than assumed.

 

If reception here is positive I'll open a PR against

RsyncProject/rsync and link this thread from it.

 

Patch inline below.  Also happy to attach as a file or send via

git-send-email if anyone prefers that form.

 

Thanks,

Deepak Jain

AiNET Factory

[email protected]

 

 

---

NEWS.md                      | 11 +++++

checksum.c                   | 20 +++++++++

lib/md-defines.h             | 12 +++++

rsync.1.md                   |  7 +++

testsuite/checksum-sha3.test | 60 ++++++++++++++++++++++++

5 files changed, 110 insertions(+)

create mode 100755 testsuite/checksum-sha3.test

 

diff --git a/NEWS.md b/NEWS.md

index c4a73d6..3c659ba 100644

--- a/NEWS.md

+++ b/NEWS.md

@@ -2,6 +2,17 @@

 ## Changes in this version:

+### ENHANCEMENTS:

+

+- Added SHA-3 (FIPS 202) as an opt-in `--checksum-choice` algorithm

+  family: `sha3-256`, `sha3-384`, and `sha3-512`. The implementation

+  uses OpenSSL EVP and so requires that rsync be built with OpenSSL

+  support and linked against libcrypto >= 1.1.1. Like `sha256` and

+  `sha512`, SHA-3 is never picked by `auto` negotiation; it must be

+  explicitly requested on both ends (e.g.

+  `--checksum-choice=sha3-256`) or made part of the

+  `RSYNC_CHECKSUM_LIST` environment override.

+

### BUG FIXES:

 - Fixed a regression introduced by the 3.4.0 secure_relative_open()

diff --git a/checksum.c b/checksum.c

index 24e46bf..f357aa5 100644

--- a/checksum.c

+++ b/checksum.c

@@ -54,6 +54,15 @@ struct name_num_item valid_checksums_items[] = {

#ifdef SUPPORT_XXHASH

             { CSUM_XXH64, 0, "xxh64", NULL },

             { CSUM_XXH64, 0, "xxhash", NULL },

+#endif

+#ifdef USE_OPENSSL

+            /* SHA-3 is offered only via OpenSSL EVP (sha3-* digests,
OpenSSL >= 1.1.1).

+            * verify_digest() probes each entry at startup and disables any
that the

+            * linked libcrypto does not support, so listing them here is
safe even

+            * when the build environment is older. */

+            { CSUM_SHA3_512, NNI_EVP, "sha3-512", NULL },

+            { CSUM_SHA3_384, NNI_EVP, "sha3-384", NULL },

+            { CSUM_SHA3_256, NNI_EVP, "sha3-256", NULL },

#endif

             { CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL },

             { CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL },

@@ -237,6 +246,14 @@ int csum_len_for_type(int cst, BOOL flist_csum)

#ifdef SHA512_DIGEST_LENGTH

               case CSUM_SHA512:

                            return SHA512_DIGEST_LENGTH;

+#endif

+#ifdef USE_OPENSSL

+              case CSUM_SHA3_256:

+                          return SHA3_256_DIGEST_LEN;

+              case CSUM_SHA3_384:

+                           return SHA3_384_DIGEST_LEN;

+              case CSUM_SHA3_512:

+                           return SHA3_512_DIGEST_LEN;

#endif

               case CSUM_XXH64:

               case CSUM_XXH3_64:

@@ -266,6 +283,9 @@ int canonical_checksum(int csum_type)

               case CSUM_SHA1:

               case CSUM_SHA256:

               case CSUM_SHA512:

+              case CSUM_SHA3_256:

+              case CSUM_SHA3_384:

+              case CSUM_SHA3_512:

                            return -1;

               case CSUM_XXH64:

               case CSUM_XXH3_64:

diff --git a/lib/md-defines.h b/lib/md-defines.h

index 6ef6a68..0b58c57 100644

--- a/lib/md-defines.h

+++ b/lib/md-defines.h

@@ -10,6 +10,15 @@

 #define MD4_DIGEST_LEN 16

#define MD5_DIGEST_LEN 16

+

+/* SHA-3 (FIPS 202). OpenSSL exposes these via EVP only, with no

+ * SHA3_*_DIGEST_LENGTH macros, so define our own. SHA3-512 shares

+ * its 64-byte digest length with SHA-512, so MAX_DIGEST_LEN is

+ * unaffected. */

+#define SHA3_256_DIGEST_LEN 32

+#define SHA3_384_DIGEST_LEN 48

+#define SHA3_512_DIGEST_LEN 64

+

#if defined SHA512_DIGEST_LENGTH

#define MAX_DIGEST_LEN SHA512_DIGEST_LENGTH

#elif defined SHA256_DIGEST_LENGTH

@@ -35,3 +44,6 @@

#define CSUM_SHA1 9

#define CSUM_SHA256 10

#define CSUM_SHA512 11

+#define CSUM_SHA3_256 12

+#define CSUM_SHA3_384 13

+#define CSUM_SHA3_512 14

diff --git a/rsync.1.md b/rsync.1.md

index 2b4b750..0e51d44 100644

--- a/rsync.1.md

+++ b/rsync.1.md

@@ -1781,11 +1781,18 @@ expand it.

     - `xxh128`

     - `xxh3`

     - `xxh64` (aka `xxhash`)

+    - `sha3-512`

+    - `sha3-384`

+    - `sha3-256`

     - `md5`

     - `md4`

     - `sha1`

     - `none`

+    The `sha3-*` choices require an rsync built with OpenSSL support and

+    a libcrypto that provides SHA-3 (OpenSSL 1.1.1 or newer).  They are

+    never selected by `auto` negotiation and must be requested explicitly.

+

     Run `rsync --version` to see the default checksum list compiled into
your

     version (which may differ from the list above).

diff --git a/testsuite/checksum-sha3.test b/testsuite/checksum-sha3.test

new file mode 100755

index 0000000..37a4a12

--- /dev/null

+++ b/testsuite/checksum-sha3.test

@@ -0,0 +1,60 @@

+#!/bin/sh

+

+# Test SHA-3 checksum support (sha3-256, sha3-384, sha3-512).

+#

+# Requires rsync to be built with OpenSSL support and linked against a

+# libcrypto that exposes the SHA-3 family (OpenSSL 1.1.1 or newer).

+# The test probes for SHA-3 by asking rsync to use it on a no-op copy;

+# if the local rsync rejects the choice (e.g. no OpenSSL at build time,

+# or older libcrypto), the test is skipped rather than failed.

+

+. "$suitedir/rsync.fns"

+

+probe() {

+    $RSYNC --checksum-choice="$1" --help >/dev/null 2>&1 || return 1

+    # --help doesn't actually parse the choice; force a real parse.

+    mkdir -p "$tmpdir/probe.src"

+    : > "$tmpdir/probe.src/x"

+    rm -rf "$tmpdir/probe.dst"

+    $RSYNC -a --checksum-choice="$1" "$tmpdir/probe.src/"
"$tmpdir/probe.dst/" 2>"$outfile"

+    rc=$?

+    rm -rf "$tmpdir/probe.src" "$tmpdir/probe.dst"

+    if [ $rc -ne 0 ] && grep -q 'unknown checksum name' "$outfile"; then

+        return 1

+    fi

+    return $rc

+}

+

+if ! probe sha3-256; then

+    test_skipped "rsync was not built with OpenSSL SHA-3 support"

+fi

+

+mkdir "$fromdir"

+mkdir "$fromdir/sub"

+# Mix of empty, small, and chunk-spanning sizes (CHUNK_SIZE is 32k
internally).

+: > "$fromdir/empty"

+echo "hello sha-3" > "$fromdir/small"

+dd if=/dev/urandom of="$fromdir/big" bs=1024 count=70 2>/dev/null

+

+for algo in sha3-256 sha3-384 sha3-512; do

+    rm -rf "$todir"

+

+    # Whole-file path (transfer checksum only).

+    checkit "$RSYNC -a --checksum-choice=$algo '$fromdir/' '$todir/'"
"$fromdir" "$todir"

+

+    # Pre-transfer + transfer checksum path (-c forces file_checksum to
run).

+    rm -rf "$todir"

+    checkit "$RSYNC -ac --checksum-choice=$algo '$fromdir/' '$todir/'"
"$fromdir" "$todir"

+

+    # --checksum-choice with distinct xfer,file pair.

+    rm -rf "$todir"

+    checkit "$RSYNC -ac --checksum-choice=$algo,$algo '$fromdir/'
'$todir/'" "$fromdir" "$todir"

+done

+

+# Delta-update path: mutate the destination, re-sync with SHA-3, confirm
match.

+rm -rf "$todir"

+$RSYNC -a --checksum-choice=sha3-256 "$fromdir/" "$todir/" || test_fail
"initial sync failed"

+echo "modified locally" >> "$todir/small"

+checkit "$RSYNC -a --checksum-choice=sha3-256 '$fromdir/' '$todir/'"
"$fromdir" "$todir"

+

+exit 0

-- 
Please use reply-all for most replies to avoid omitting the mailing list.
To unsubscribe or change options: https://lists.samba.org/mailman/listinfo/rsync
Before posting, read: http://www.catb.org/~esr/faqs/smart-questions.html

Reply via email to