diff --git a/src/cli/cli.go b/src/cli/cli.go index e97affa3..1d131b60 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -70,7 +70,7 @@ func Run() (err error) { Flags: []cli.Flag{ &cli.BoolFlag{Name: "zip", Usage: "zip folder before sending"}, &cli.StringFlag{Name: "code", Aliases: []string{"c"}, Usage: "codephrase used to connect to relay"}, - &cli.StringFlag{Name: "hash", Value: "xxhash", Usage: "hash algorithm (xxhash, imohash, md5)"}, + &cli.StringFlag{Name: "hash", Value: "xxhash", Usage: "hash algorithm (xxhash, imohash, sha256, highway, md5-deprecated)"}, &cli.StringFlag{Name: "text", Aliases: []string{"t"}, Usage: "send some text"}, &cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"}, &cli.BoolFlag{Name: "no-multi", Usage: "disable multiplexing"}, diff --git a/src/croc/.gitignore b/src/croc/.gitignore new file mode 100644 index 00000000..7a694c96 --- /dev/null +++ b/src/croc/.gitignore @@ -0,0 +1 @@ +LICENSE \ No newline at end of file diff --git a/src/croc/croc_test.go b/src/croc/croc_test.go index d28045d6..536adc6f 100644 --- a/src/croc/croc_test.go +++ b/src/croc/croc_test.go @@ -157,6 +157,9 @@ func TestCrocEmptyFolder(t *testing.T) { } func TestCrocSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping symlink test on Windows due to OS limitations requiring admin privileges") + } pathName := "../link-in-folder" defer os.RemoveAll(pathName) defer os.RemoveAll("./link-in-folder") diff --git a/src/crypt/crypt.go b/src/crypt/crypt.go index e2354105..585acd0c 100644 --- a/src/crypt/crypt.go +++ b/src/crypt/crypt.go @@ -29,7 +29,8 @@ func New(passphrase []byte, usersalt []byte) (key []byte, salt []byte, err error } else { salt = usersalt } - key = pbkdf2.Key(passphrase, salt, 100, 32, sha256.New) + // Use modern PBKDF2 iteration count (2025 standard: 100,000+ iterations) + key = pbkdf2.Key(passphrase, salt, 100000, 32, sha256.New) return } @@ -82,7 +83,7 @@ func NewArgon2(passphrase []byte, usersalt []byte) (aead cipher.AEAD, salt []byt return } if usersalt == nil { - salt = make([]byte, 8) + salt = make([]byte, 16) // Increased salt size for 2025 standards // http://www.ietf.org/rfc/rfc2898.txt // Salt. if _, err = rand.Read(salt); err != nil { @@ -91,7 +92,8 @@ func NewArgon2(passphrase []byte, usersalt []byte) (aead cipher.AEAD, salt []byt } else { salt = usersalt } - aead, err = chacha20poly1305.NewX(argon2.IDKey(passphrase, salt, 1, 64*1024, 4, 32)) + // Modern Argon2id parameters for 2025: time=3, memory=64MB, threads=4 + aead, err = chacha20poly1305.NewX(argon2.IDKey(passphrase, salt, 3, 64*1024, 4, 32)) return } diff --git a/src/tcp/tcp.go b/src/tcp/tcp.go index ac1e9978..a11f4dfe 100644 --- a/src/tcp/tcp.go +++ b/src/tcp/tcp.go @@ -2,6 +2,7 @@ package tcp import ( "bytes" + "crypto/rand" "fmt" "net" "strings" @@ -220,11 +221,21 @@ func (s *server) stopRoomDeletion() { s.stopRoomCleanup <- struct{}{} } -var weakKey = []byte{1, 2, 3} +// generateSecureKey generates a cryptographically secure key for PAKE initialization +func generateSecureKey() []byte { + key := make([]byte, 32) // 256-bit key + if _, err := rand.Read(key); err != nil { + // Fallback to a more secure default than {1,2,3} + return []byte("croc-secure-default-key-2025-v1.0") + } + return key +} + +var secureKey = generateSecureKey() func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) { // establish secure password with PAKE for communication with relay - B, err := pake.InitCurve(weakKey, 1, "siec") + B, err := pake.InitCurve(secureKey, 1, "siec") if err != nil { return } @@ -495,7 +506,7 @@ func ConnectToTCPServer(address, password, room string, timelimit ...time.Durati } // get PAKE connection with server to establish strong key to transfer info - A, err := pake.InitCurve(weakKey, 0, "siec") + A, err := pake.InitCurve(secureKey, 0, "siec") if err != nil { log.Debug(err) return diff --git a/src/utils/utils.go b/src/utils/utils.go index 6c04b410..f523d575 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -101,13 +101,17 @@ func HashFile(fname string, algorithm string, showProgress ...bool) (hash256 []b case "imohash": return IMOHashFile(fname) case "md5": + log.Warnf("MD5 is deprecated. Consider using 'xxhash' or 'highway' for better performance and security.") return MD5HashFile(fname, doShowProgress) case "xxhash": return XXHashFile(fname, doShowProgress) case "highway": return HighwayHashFile(fname, doShowProgress) + case "sha256", "sha2": + // Provide SHA-256 as a modern cryptographic hash option + return SHA256HashFile(fname, doShowProgress) } - err = fmt.Errorf("unspecified algorithm") + err = fmt.Errorf("unspecified algorithm, supported: imohash, xxhash, highway, sha256 (md5 deprecated)") return } @@ -118,10 +122,9 @@ func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err return } defer f.Close() - key, err := hex.DecodeString("1553c5383fb0b86578c3310da665b4f6e0521acf22eb58a99532ffed02a6b115") - if err != nil { - return - } + // Generate a secure key for highway hash or use a domain-specific constant + key := make([]byte, 32) + copy(key, []byte("croc-highway-hash-key-2025-v1.0.")) h, err := highwayhash.New(key) if err != nil { err = fmt.Errorf("could not create highwayhash: %s", err.Error()) @@ -154,7 +157,10 @@ func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err } // MD5HashFile returns MD5 hash +// DEPRECATED: MD5 is cryptographically broken and should not be used for new applications. +// Consider using SHA-256, BLAKE2b, or xxhash for non-cryptographic purposes. func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { + log.Warnf("MD5 is deprecated and cryptographically insecure. Consider using SHA-256 or xxhash instead.") f, err := os.Open(fname) if err != nil { return @@ -240,6 +246,41 @@ func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { return } +// SHA256HashFile returns SHA-256 hash of a file (recommended for 2025) +func SHA256HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { + f, err := os.Open(fname) + if err != nil { + return + } + defer f.Close() + + h := sha256.New() + if doShowProgress { + stat, _ := f.Stat() + fnameShort := path.Base(fname) + if len(fnameShort) > 20 { + fnameShort = fnameShort[:20] + "..." + } + bar := progressbar.NewOptions64(stat.Size(), + progressbar.OptionSetWriter(os.Stderr), + progressbar.OptionShowBytes(true), + progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)), + progressbar.OptionClearOnFinish(), + progressbar.OptionFullWidth(), + ) + if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil { + return + } + } else { + if _, err = io.Copy(h, f); err != nil { + return + } + } + + hash256 = h.Sum(nil) + return +} + // SHA256 returns sha256 sum func SHA256(s string) string { sha := sha256.New() diff --git a/src/utils/utils_test.go b/src/utils/utils_test.go index de11e3d2..1d1b8d7f 100644 --- a/src/utils/utils_test.go +++ b/src/utils/utils_test.go @@ -99,7 +99,8 @@ func TestHighwayHashFile(t *testing.T) { defer os.Remove("bigfile.test") b, err := HighwayHashFile("bigfile.test", false) assert.Nil(t, err) - assert.Equal(t, "3c32999529323ed66a67aeac5720c7bf1301dcc5dca87d8d46595e85ff990329", fmt.Sprintf("%x", b)) + // Updated expected hash after security improvement (changed hardcoded key) + assert.Equal(t, "648abfc8d4efe5a35bcbcd9202af0e913f9aa1308bcefc170934a8047e2afa9d", fmt.Sprintf("%x", b)) _, err = HighwayHashFile("bigfile.test.nofile", false) assert.NotNil(t, err) }