code.gitea.io/gitea@v1.19.3/modules/auth/password/hash/hash_test.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package hash
     5  
     6  import (
     7  	"encoding/hex"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  type testSaltHasher string
    16  
    17  func (t testSaltHasher) HashWithSaltBytes(password string, salt []byte) string {
    18  	return password + "$" + string(salt) + "$" + string(t)
    19  }
    20  
    21  func Test_registerHasher(t *testing.T) {
    22  	MustRegister("Test_registerHasher", func(config string) testSaltHasher {
    23  		return testSaltHasher(config)
    24  	})
    25  
    26  	assert.Panics(t, func() {
    27  		MustRegister("Test_registerHasher", func(config string) testSaltHasher {
    28  			return testSaltHasher(config)
    29  		})
    30  	})
    31  
    32  	assert.Error(t, Register("Test_registerHasher", func(config string) testSaltHasher {
    33  		return testSaltHasher(config)
    34  	}))
    35  
    36  	assert.Equal(t, "password$salt$",
    37  		Parse("Test_registerHasher").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
    38  
    39  	assert.Equal(t, "password$salt$config",
    40  		Parse("Test_registerHasher$config").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
    41  
    42  	delete(availableHasherFactories, "Test_registerHasher")
    43  }
    44  
    45  func TestParse(t *testing.T) {
    46  	hashAlgorithmsToTest := []string{}
    47  	for plainHashAlgorithmNames := range availableHasherFactories {
    48  		hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
    49  	}
    50  	for _, aliased := range aliasAlgorithmNames {
    51  		if strings.Contains(aliased, "$") {
    52  			hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
    53  		}
    54  	}
    55  	for _, algorithmName := range hashAlgorithmsToTest {
    56  		t.Run(algorithmName, func(t *testing.T) {
    57  			algo := Parse(algorithmName)
    58  			assert.NotNil(t, algo, "Algorithm %s resulted in an empty algorithm", algorithmName)
    59  		})
    60  	}
    61  }
    62  
    63  func TestHashing(t *testing.T) {
    64  	hashAlgorithmsToTest := []string{}
    65  	for plainHashAlgorithmNames := range availableHasherFactories {
    66  		hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
    67  	}
    68  	for _, aliased := range aliasAlgorithmNames {
    69  		if strings.Contains(aliased, "$") {
    70  			hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
    71  		}
    72  	}
    73  
    74  	runTests := func(password, salt string, shouldPass bool) {
    75  		for _, algorithmName := range hashAlgorithmsToTest {
    76  			t.Run(algorithmName, func(t *testing.T) {
    77  				output, err := Parse(algorithmName).Hash(password, salt)
    78  				if shouldPass {
    79  					assert.NoError(t, err)
    80  					assert.NotEmpty(t, output, "output for %s was empty", algorithmName)
    81  				} else {
    82  					assert.Error(t, err)
    83  				}
    84  
    85  				assert.Equal(t, Parse(algorithmName).VerifyPassword(password, output, salt), shouldPass)
    86  			})
    87  		}
    88  	}
    89  
    90  	// Test with new salt format.
    91  	runTests(strings.Repeat("a", 16), hex.EncodeToString([]byte{0x01, 0x02, 0x03}), true)
    92  
    93  	// Test with legacy salt format.
    94  	runTests(strings.Repeat("a", 16), strings.Repeat("b", 10), true)
    95  
    96  	// Test with invalid salt.
    97  	runTests(strings.Repeat("a", 16), "a", false)
    98  }
    99  
   100  // vectors were generated using the current codebase.
   101  var vectors = []struct {
   102  	algorithms []string
   103  	password   string
   104  	salt       string
   105  	output     string
   106  	shouldfail bool
   107  }{
   108  	{
   109  		algorithms: []string{"bcrypt", "bcrypt$10"},
   110  		password:   "abcdef",
   111  		salt:       strings.Repeat("a", 10),
   112  		output:     "$2a$10$fjtm8BsQ2crym01/piJroenO3oSVUBhSLKaGdTYJ4tG0ePVCrU0G2",
   113  		shouldfail: false,
   114  	},
   115  	{
   116  		algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
   117  		password:   "abcdef",
   118  		salt:       strings.Repeat("a", 10),
   119  		output:     "3b571d0c07c62d42b7bad3dbf18fb0cd67d4d8cd4ad4c6928e1090e5b2a4a84437c6fd2627d897c0e7e65025ca62b67a0002",
   120  		shouldfail: false,
   121  	},
   122  	{
   123  		algorithms: []string{"argon2", "argon2$2$65536$8$50"},
   124  		password:   "abcdef",
   125  		salt:       strings.Repeat("a", 10),
   126  		output:     "551f089f570f989975b6f7c6a8ff3cf89bc486dd7bbe87ed4d80ad4362f8ee599ec8dda78dac196301b98456402bcda775dc",
   127  		shouldfail: false,
   128  	},
   129  	{
   130  		algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
   131  		password:   "abcdef",
   132  		salt:       strings.Repeat("a", 10),
   133  		output:     "ab48d5471b7e6ed42d10001db88c852ff7303c788e49da5c3c7b63d5adf96360303724b74b679223a3dea8a242d10abb1913",
   134  		shouldfail: false,
   135  	},
   136  	{
   137  		algorithms: []string{"bcrypt", "bcrypt$10"},
   138  		password:   "abcdef",
   139  		salt:       hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
   140  		output:     "$2a$10$qhgm32w9ZpqLygugWJsLjey8xRGcaq9iXAfmCeNBXxddgyoaOC3Gq",
   141  		shouldfail: false,
   142  	},
   143  	{
   144  		algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
   145  		password:   "abcdef",
   146  		salt:       hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
   147  		output:     "25fe5f66b43fa4eb7b6717905317cd2223cf841092dc8e0a1e8c75720ad4846cb5d9387303e14bc3c69faa3b1c51ef4b7de1",
   148  		shouldfail: false,
   149  	},
   150  	{
   151  		algorithms: []string{"argon2", "argon2$2$65536$8$50"},
   152  		password:   "abcdef",
   153  		salt:       hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
   154  		output:     "9c287db63a91d18bb1414b703216da4fc431387c1ae7c8acdb280222f11f0929831055dbfd5126a3b48566692e83ec750d2a",
   155  		shouldfail: false,
   156  	},
   157  	{
   158  		algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
   159  		password:   "abcdef",
   160  		salt:       hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
   161  		output:     "45d6cdc843d65cf0eda7b90ab41435762a282f7df013477a1c5b212ba81dbdca2edf1ecc4b5cb05956bb9e0c37ab29315d78",
   162  		shouldfail: false,
   163  	},
   164  	{
   165  		algorithms: []string{"pbkdf2$320000$50"},
   166  		password:   "abcdef",
   167  		salt:       hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
   168  		output:     "84e233114499e8721da80e85568e5b7b5900b3e49a30845fcda9d1e1756da4547d70f8740ac2b4a5d82f88cebcd27f21bfe2",
   169  		shouldfail: false,
   170  	},
   171  	{
   172  		algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
   173  		password:   "abcdef",
   174  		salt:       "",
   175  		output:     "",
   176  		shouldfail: true,
   177  	},
   178  }
   179  
   180  // Ensure that the current code will correctly verify against the test vectors.
   181  func TestVectors(t *testing.T) {
   182  	for i, vector := range vectors {
   183  		for _, algorithm := range vector.algorithms {
   184  			t.Run(strconv.Itoa(i)+": "+algorithm, func(t *testing.T) {
   185  				pa := Parse(algorithm)
   186  				assert.Equal(t, !vector.shouldfail, pa.VerifyPassword(vector.password, vector.output, vector.salt))
   187  			})
   188  		}
   189  	}
   190  }