// 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)) }