softether-go/pkg/client/crypto.go
Git Sagar 829ca73b1b initial commit: standalone SoftEther VPN client in Go
Built-in DHCP (raw Ethernet frames through tunnel), automatic reconnection,
host route management, classless static routes (option 121/249), DNS config.
Single static binary, Linux only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 16:13:51 +05:30

137 lines
3.5 KiB
Go

// SHA-0 implementation for SoftEther VPN password authentication.
//
// SoftEther uses SHA-0 (the original, withdrawn FIPS-180 hash — NOT SHA-1) for
// password hashing and the SecurePassword challenge-response. SHA-0 differs from
// SHA-1 only in that the message schedule expansion does NOT include a left-rotate
// by 1 bit (see transform() below).
//
// Auth flow:
// 1. Client hashes the plaintext password: HashedPassword = SHA0(password)
// 2. Server sends a 20-byte random challenge in the Hello pack
// 3. Client computes: SecurePassword = SHA0(HashedPassword + ServerRandom)
// 4. Client sends SecurePassword in the auth pack
//
// Reference C implementation:
// https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Mayaqua/Encrypt.c#L1088 (Sha0)
// https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L105 (SecurePassword)
package client
const sha1Size = 20
func rol(bits int, value uint32) uint32 {
return (value << bits) | (value >> (32 - bits))
}
type sha0ctx struct {
count uint64
buf [64]byte
state [8]uint32
}
func (c *sha0ctx) init() {
c.state[0] = 0x67452301
c.state[1] = 0xEFCDAB89
c.state[2] = 0x98BADCFE
c.state[3] = 0x10325476
c.state[4] = 0xC3D2E1F0
c.count = 0
}
func (c *sha0ctx) transform() {
var W [80]uint32
p := 0
t := 0
for ; t < 16; t++ {
W[t] = uint32(c.buf[p])<<24 | uint32(c.buf[p+1])<<16 | uint32(c.buf[p+2])<<8 | uint32(c.buf[p+3])
p += 4
}
// SHA-0: W[t] = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16] (NO rotate)
// SHA-1 would be: W[t] = rol(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16])
for ; t < 80; t++ {
W[t] = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]
}
A, B, C, D, E := c.state[0], c.state[1], c.state[2], c.state[3], c.state[4]
for t = 0; t < 80; t++ {
tmp := rol(5, A) + E + W[t]
if t < 20 {
tmp += (D ^ (B & (C ^ D))) + 0x5A827999
} else if t < 40 {
tmp += (B ^ C ^ D) + 0x6ED9EBA1
} else if t < 60 {
tmp += ((B & C) | (D & (B | C))) + 0x8F1BBCDC
} else {
tmp += (B ^ C ^ D) + 0xCA62C1D6
}
E = D
D = C
C = rol(30, B)
B = A
A = tmp
}
c.state[0] += A
c.state[1] += B
c.state[2] += C
c.state[3] += D
c.state[4] += E
}
func (c *sha0ctx) update(data []byte) {
i := int(c.count & 63)
c.count += uint64(len(data))
for _, d := range data {
c.buf[i] = d
i++
if i == 64 {
c.transform()
i = 0
}
}
}
func (c *sha0ctx) final() {
cnt := c.count * 8
c.update([]byte{0x80})
for c.count&63 != 56 {
c.update([]byte{0x0})
}
for i := 0; i < 8; i++ {
c.update([]byte{byte(cnt >> ((7 - i) * 8))})
}
p := 0
for i := 0; i < 5; i++ {
c.buf[p] = byte(c.state[i] >> 24)
c.buf[p+1] = byte(c.state[i] >> 16)
c.buf[p+2] = byte(c.state[i] >> 8)
c.buf[p+3] = byte(c.state[i])
p += 4
}
}
func sha0(data []byte) [sha1Size]byte {
var c sha0ctx
c.init()
c.update(data)
c.final()
var out [sha1Size]byte
copy(out[:], c.buf[:sha1Size])
return out
}
// SecurePassword computes the challenge-response: SHA0(HashedPassword + ServerRandom).
// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L105
func SecurePassword(hashedPassword, random [sha1Size]byte) [sha1Size]byte {
buf := make([]byte, sha1Size*2)
copy(buf, hashedPassword[:])
copy(buf[sha1Size:], random[:])
return sha0(buf)
}
// HashPassword computes SHA0(password) — the first step of SoftEther password auth.
// The result is what SoftEther stores server-side as "HashedPassword".
// See: https://github.com/SoftEtherVPN/SoftEtherVPN/blob/v5.02.5187/src/Cedar/Sam.c#L91 (HashPassword)
func HashPassword(password string) [sha1Size]byte {
return sha0([]byte(password))
}