mirror of
https://github.com/therealpaulgg/ssh-sync.git
synced 2026-04-26 16:05:51 +03:00
[PR #77] Migrate from ECDSA to post-quantum cryptography (ML-DSA-65 + ML-KEM-768) #81
Labels
No labels
ai-generated
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/ssh-sync#81
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/therealpaulgg/ssh-sync/pull/77
Author: @therealpaulgg
Created: 2/13/2026
Status: 🔄 Open
Base:
main← Head:claude/quantum-resistant-ssh-sync-LzCo1📝 Commits (10+)
10e6cb4feat: replace classical cryptography with post-quantum algorithms (ML-KEM-768 + ML-DSA-65)23ae333feat: add backward-compatible migration path for legacy EC keys5169b8bfeat: addmigratecommand for upgrading keys to post-quantum cryptofe9a0a0feat: unify PQ keypairs into single master seed with HKDF derivation92454edfeat: add --classic flag to setup for optional EC key generation26ae62afix: match generateKeyClassic signature to original generateKey361f82crefactor: remove dead legacy PQ PEM format support908795erefactor: separate signing and encapsulation keys in PublicKeyDto0c78a10start using shared library for my sanity360a33bremove go work📊 Changes
29 files changed (+1059 additions, -325 deletions)
View changed files
📝
go.mod(+6 -6)📝
go.sum(+10 -7)📝
main.go(+13 -1)📝
pkg/actions/challenge-response.go(+11 -5)📝
pkg/actions/download.go(+1 -1)📝
pkg/actions/interactive/states/delete-ssh-key.go(+1 -1)📝
pkg/actions/interactive/states/ssh-key-content.go(+1 -1)📝
pkg/actions/interactive/states/ssh-key-manager.go(+1 -1)📝
pkg/actions/interactive/states/ssh-key-options.go(+1 -1)➕
pkg/actions/migrate.go(+183 -0)📝
pkg/actions/remove-machine.go(+1 -1)📝
pkg/actions/reset.go(+2 -1)📝
pkg/actions/setup.go(+105 -53)📝
pkg/actions/upload.go(+3 -2)➖
pkg/dto/main.go(+0 -82)📝
pkg/retrieval/data.go(+6 -4)📝
pkg/retrieval/data_test.go(+47 -22)➕
pkg/retrieval/deps.go(+6 -0)📝
pkg/retrieval/machines.go(+5 -4)📝
pkg/retrieval/machines_test.go(+4 -1)...and 9 more files
📄 Description
Summary
This PR adds post-quantum cryptography support using ML-DSA-65 (FIPS 204) for digital signatures and ML-KEM-768 (FIPS 203) for key encapsulation, while preserving full backward compatibility with existing ECDSA/ECDH-ES users.
Key Changes
Cryptographic Algorithm Addition
filippo.io/mldsa(new default for fresh setups)crypto/mlkem(replaces ECDH/JWE for PQ users)lestrrat-go/jwx/v2is still a dependencyKey Format Auto-Detection (
pkg/utils/keyformat.go)DetectKeyFormat()inspects the PEM block type in~/.ssh-sync/keypair"EC PRIVATE KEY"→FormatLegacyEC"SSHSYNC PQ MASTER SEED"→FormatPostQuantumEncrypt,Decrypt,GetToken) branch on this at runtimeKey Storage Format (PQ path)
"SSHSYNC PQ MASTER SEED"PEM block in~/.ssh-sync/keypairkeypair.pubcontains the ML-DSA-65 public key (for server identity/JWT verification)Key Generation (
pkg/actions/setup.go)generateKey()generates a 64-byte random master seed and writes a"SSHSYNC PQ MASTER SEED"PEM blockgenerateKeyClassic()(legacy ECDSA P-256) unchanged--classicflag routes to the old path; default is now PQKey Derivation (
pkg/utils/pqseed.go)DeriveMLDSAKey(seed)— HKDF-SHA256 with label"ssh-sync-mldsa-v1"→ ML-DSA-65 keypairDeriveMLKEMKey(seed)— HKDF-SHA256 with label"ssh-sync-mlkem768-v1"→ ML-KEM-768 keypairKey Retrieval (
pkg/utils/keyretrieval.go)RetrieveSigningKey()— derives ML-DSA-65 private key from master seedRetrieveDecapsulationKey()/RetrieveEncapsulationKey()— derives ML-KEM-768 keys from master seedBuildPQPublicKeys()— returns ML-DSA-65 public key PEM + ML-KEM-768 encapsulation key PEM as separate fields (sent to server during existing-machine setup)RetrievePrivateKey()/RetrievePublicKey()(JWK) unchangedJWT Token Generation (
pkg/utils/tokengen.go)getTokenPQ()— manual JWT construction signed with ML-DSA-65; uses"alg": "MLDSA"headergetTokenLegacy()— existing ES512 path unchangedGetToken()auto-detects format and dispatchesEncryption/Decryption (
pkg/utils/encrypt.go,pkg/utils/decrypt.go)EncryptMLKEM/DecryptMLKEM— ML-KEM-768 encapsulation → HKDF → AES-256-GCM[1088B ML-KEM ciphertext][12B nonce][AES-GCM ciphertext+tag]EncryptWithPQPublicKey— encrypts for a remote machine's ML-KEM encapsulation key (challenge-response)EncryptWithECPublicKey— legacy path unchangedEncrypt/Decryptauto-detect formatChallenge-Response (
pkg/actions/challenge-response.go)EncapsulationKey, usesEncryptWithPQPublicKey; otherwise falls back toEncryptWithECPublicKeyMigration Command (
pkg/actions/migrate.go) — newmigratecommand for existing EC users to upgrade to PQ in-place:Dependencies
filippo.io/mldsa(ML-DSA-65),github.com/therealpaulgg/ssh-sync-common,golang.org/x/crypto(promoted to direct)github.com/lestrrat-go/jwx/v2(still used for legacy EC token signing and JWE encryption/decryption)crypto/mlkem(Go 1.25+)Backward Compatibility
--classicflag available on setup to explicitly generate a legacy EC keypairmigratecommand provides an explicit upgrade path from EC to PQ🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.