mirror of
				https://github.com/owncast/owncast.git
				synced 2025-11-03 21:08:36 +08:00 
			
		
		
		
	* sha-512 hash passwords longer than 72 bytes * rename compress_hashing to go conventions * add api test for long passwords * fix typo * chore(test): add unit test for password hashing --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com>
		
			
				
	
	
		
			321 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package utils
 | 
						|
 | 
						|
import (
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"golang.org/x/crypto/bcrypt"
 | 
						|
)
 | 
						|
 | 
						|
func TestHashPassword_ShortPassword(t *testing.T) {
 | 
						|
	password := "short"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for short password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == "" {
 | 
						|
		t.Error("HashPassword returned empty hash for short password")
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == password {
 | 
						|
		t.Error("HashPassword returned unhashed password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_72BytePassword(t *testing.T) {
 | 
						|
	// Test with exactly 72 bytes (bcrypt's limit)
 | 
						|
	password := strings.Repeat("a", 72)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for 72-byte password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == "" {
 | 
						|
		t.Error("HashPassword returned empty hash for 72-byte password")
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == password {
 | 
						|
		t.Error("HashPassword returned unhashed password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_LongPassword(t *testing.T) {
 | 
						|
	// Test with password longer than 72 bytes
 | 
						|
	password := strings.Repeat("a", 100)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for long password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == "" {
 | 
						|
		t.Error("HashPassword returned empty hash for long password")
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == password {
 | 
						|
		t.Error("HashPassword returned unhashed password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_VeryLongPassword(t *testing.T) {
 | 
						|
	// Test with very long password (200 bytes)
 | 
						|
	password := strings.Repeat("x", 200)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for very long password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if hash == "" {
 | 
						|
		t.Error("HashPassword returned empty hash for very long password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_ShortPassword_Success(t *testing.T) {
 | 
						|
	password := "testpassword123"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("CompareHash failed to match correct short password: %v", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_ShortPassword_Failure(t *testing.T) {
 | 
						|
	password := "testpassword123"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, "wrongpassword")
 | 
						|
	if err == nil {
 | 
						|
		t.Error("CompareHash incorrectly matched wrong password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_LongPassword_Success(t *testing.T) {
 | 
						|
	// Test with password longer than 72 bytes
 | 
						|
	password := strings.Repeat("a", 100)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed for long password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("CompareHash failed to match correct long password: %v", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_LongPassword_Failure(t *testing.T) {
 | 
						|
	// Test with password longer than 72 bytes
 | 
						|
	password := strings.Repeat("a", 100)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Wrong password also longer than 72 bytes
 | 
						|
	wrongPassword := strings.Repeat("b", 100)
 | 
						|
	err = CompareHash(hash, wrongPassword)
 | 
						|
	if err == nil {
 | 
						|
		t.Error("CompareHash incorrectly matched wrong long password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_LongPassword_SlightDifference(t *testing.T) {
 | 
						|
	// Test that even a slight difference in long passwords is detected
 | 
						|
	password := strings.Repeat("a", 100)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Same length but one character different
 | 
						|
	wrongPassword := strings.Repeat("a", 99) + "b"
 | 
						|
	err = CompareHash(hash, wrongPassword)
 | 
						|
	if err == nil {
 | 
						|
		t.Error("CompareHash incorrectly matched password with slight difference")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompareHash_73BytePassword(t *testing.T) {
 | 
						|
	// Test edge case: 73 bytes (just over bcrypt's limit)
 | 
						|
	password := strings.Repeat("a", 73)
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed for 73-byte password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("CompareHash failed to match correct 73-byte password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure wrong password doesn't match
 | 
						|
	wrongPassword := strings.Repeat("b", 73)
 | 
						|
	err = CompareHash(hash, wrongPassword)
 | 
						|
	if err == nil {
 | 
						|
		t.Error("CompareHash incorrectly matched wrong 73-byte password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressPassword_ShortPassword(t *testing.T) {
 | 
						|
	password := []byte("short")
 | 
						|
	compressed := compressPassword(password)
 | 
						|
 | 
						|
	if len(compressed) != len(password) {
 | 
						|
		t.Error("compressPassword should not modify passwords shorter than 72 bytes")
 | 
						|
	}
 | 
						|
 | 
						|
	if string(compressed) != string(password) {
 | 
						|
		t.Error("compressPassword modified short password content")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressPassword_72BytePassword(t *testing.T) {
 | 
						|
	password := []byte(strings.Repeat("a", 72))
 | 
						|
	compressed := compressPassword(password)
 | 
						|
 | 
						|
	if len(compressed) != 72 {
 | 
						|
		t.Error("compressPassword should not modify passwords exactly 72 bytes")
 | 
						|
	}
 | 
						|
 | 
						|
	if string(compressed) != string(password) {
 | 
						|
		t.Error("compressPassword modified 72-byte password content")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressPassword_LongPassword(t *testing.T) {
 | 
						|
	password := []byte(strings.Repeat("a", 100))
 | 
						|
	compressed := compressPassword(password)
 | 
						|
 | 
						|
	// SHA-512 produces 64 bytes
 | 
						|
	if len(compressed) != 64 {
 | 
						|
		t.Errorf("compressPassword should produce 64-byte hash, got %d bytes", len(compressed))
 | 
						|
	}
 | 
						|
 | 
						|
	// Ensure it's different from original
 | 
						|
	if string(compressed) == string(password) {
 | 
						|
		t.Error("compressPassword did not hash long password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCompressPassword_DifferentLongPasswords(t *testing.T) {
 | 
						|
	password1 := []byte(strings.Repeat("a", 100))
 | 
						|
	password2 := []byte(strings.Repeat("b", 100))
 | 
						|
 | 
						|
	compressed1 := compressPassword(password1)
 | 
						|
	compressed2 := compressPassword(password2)
 | 
						|
 | 
						|
	if string(compressed1) == string(compressed2) {
 | 
						|
		t.Error("compressPassword produced same hash for different long passwords")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_UniqueHashes(t *testing.T) {
 | 
						|
	password := "testpassword"
 | 
						|
 | 
						|
	// Generate two hashes of the same password
 | 
						|
	hash1, err1 := HashPassword(password)
 | 
						|
	hash2, err2 := HashPassword(password)
 | 
						|
 | 
						|
	if err1 != nil || err2 != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v, %v", err1, err2)
 | 
						|
	}
 | 
						|
 | 
						|
	// bcrypt includes a salt, so hashes should be different
 | 
						|
	if hash1 == hash2 {
 | 
						|
		t.Error("HashPassword produced identical hashes (should use salt)")
 | 
						|
	}
 | 
						|
 | 
						|
	// But both should validate against the original password
 | 
						|
	if err := CompareHash(hash1, password); err != nil {
 | 
						|
		t.Error("First hash does not validate against password")
 | 
						|
	}
 | 
						|
 | 
						|
	if err := CompareHash(hash2, password); err != nil {
 | 
						|
		t.Error("Second hash does not validate against password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_EmptyPassword(t *testing.T) {
 | 
						|
	password := ""
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for empty password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Error("CompareHash failed to match empty password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_SpecialCharacters(t *testing.T) {
 | 
						|
	password := "p@ssw0rd!#$%^&*()"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for password with special characters: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Error("CompareHash failed to match password with special characters")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_UnicodeCharacters(t *testing.T) {
 | 
						|
	password := "пароль密码🔒"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for password with unicode characters: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Error("CompareHash failed to match password with unicode characters")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_LongUnicodePassword(t *testing.T) {
 | 
						|
	// Create a long password with unicode characters (over 72 bytes)
 | 
						|
	password := strings.Repeat("密码🔒", 30) // Each character is multiple bytes
 | 
						|
 | 
						|
	if len([]byte(password)) <= 72 {
 | 
						|
		t.Skip("Unicode password not long enough for test")
 | 
						|
	}
 | 
						|
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("HashPassword failed for long unicode password: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = CompareHash(hash, password)
 | 
						|
	if err != nil {
 | 
						|
		t.Error("CompareHash failed to match long unicode password")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHashPassword_BcryptCostIsDefault(t *testing.T) {
 | 
						|
	password := "testpassword"
 | 
						|
	hash, err := HashPassword(password)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("HashPassword failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Extract the cost from the hash
 | 
						|
	cost, err := bcrypt.Cost([]byte(hash))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Failed to extract cost from hash: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// bcrypt.DefaultCost is 10
 | 
						|
	if cost != bcrypt.DefaultCost {
 | 
						|
		t.Errorf("Expected cost %d, got %d", bcrypt.DefaultCost, cost)
 | 
						|
	}
 | 
						|
}
 |