summaryrefslogblamecommitdiffstats
path: root/lib/sha1.go
blob: 5cfff07a36c7c38bf72c10def688f15c7dcd021b (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                             






                                                                       
                  


                       

 
                      
                                        






                   































                                                  

                                                                   
                                             




















                                                              










                                                          
                                                                                




                 









































                                                                      











                                        

















                                                                 
                       
                     

                                     
                                   


                                  
                                              













                                                
                                                                        


                                                   
                                          




                                                    




                                      


















                                                        
 
                                               

                                         

 
                                                        

                                         



                           
























                                                               
// Copyright © 2021 siddharth ravikumar <s@ricketyspace.net>
// SPDX-License-Identifier: ISC

package lib

// SHA-1 implementation.
// Reference https://csrc.nist.gov/publications/detail/fips/180/4/final

type Sha1 struct {
	hvs    []uint32
	Msg    []byte
	MsgLen int
}

// Initial hash value.
var sha1IHashValues []uint32 = []uint32{
	0x67452301,
	0xefcdab89,
	0x98badcfe,
	0x10325476,
	0xc3d2e1f0,
}

// SHA-1 - Function f_t(x, y, z)
func sha1FT(t int, x, y, z uint32) uint32 {
	switch {
	case t <= 19:
		return (x & y) ^ (^x & z)
	case t >= 20 && t <= 39:
		return x ^ y ^ z
	case t >= 40 && t <= 59:
		return (x & y) ^ (x & z) ^ (y & z)
	case t >= 60 && t <= 79:
		return x ^ y ^ z
	default:
		return uint32(0)
	}
}

// SHA-1 - Constant K_t
func sha1KT(t int) uint32 {
	switch {
	case t <= 19:
		return uint32(0x5a827999)
	case t >= 20 && t <= 39:
		return uint32(0x6ed9eba1)
	case t >= 40 && t <= 59:
		return uint32(0x8f1bbcdc)
	case t >= 60 && t <= 79:
		return uint32(0xca62c1d6)
	default:
		return uint32(0)
	}
}

// Converts padded messages bytes `pm` into 512-bit message blocks.
// Each 512-bit block is an array of 16 32-bit words.
func shaMessageBlocks(pm []byte) [][]uint32 {
	// Break into 512-bit blocks
	bs := BreakIntoBlocks(pm, 64)

	mbs := make([][]uint32, 0) // Message blocks.
	for i := 0; i < len(bs); i++ {
		ws := make([]uint32, 0) // 32-bit words.

		// Break 512-bit (64 bytes) into 32-bit words.
		for j := 0; j < 64; j = j + 4 {
			// Pack 4 bytes into a 32-bit word.
			w := (uint32(bs[i][j])<<24 |
				uint32(bs[i][j+1])<<16 |
				uint32(bs[i][j+2])<<8 |
				uint32(bs[i][j+3]))
			ws = append(ws, w)
		}
		mbs = append(mbs, ws)
	}
	return mbs
}

// Returns the message schedule W_t for message black `mb`
// The message schedule has 80 32-bit words.
func sha1MessageSchedule(mb []uint32) []uint32 {
	// Message schedule.
	w := make([]uint32, 0)

	// Generate message schedule.
	for t := 0; t <= 79; t++ {
		if t <= 15 {
			w = append(w, mb[t])
		} else {
			w = append(w, shaRotl(w[t-3]^w[t-8]^w[t-14]^w[t-16], 1))
		}
	}
	return w
}

// Returns Merkle–Damgård padding in bytes for length of mesage `l`
// bytes.
func MDPadding(l int) []byte {
	l = l * 8 // msg size in bits

	// Reckon value of `k`
	k := 0
	for ((l + 1 + k) % 512) != 448 {
		k += 1
	}

	// Initialize padding bytes
	pbs := make([]byte, 0)

	// Add bit `1` as byte block.
	pbs = append(pbs, 0x80)
	f := 7 // unclaimed bits in last byte of `pbs`

	// Add `k` bit `0`s
	for i := 0; i < k; i++ {
		if f == 0 {
			pbs = append(pbs, 0x0)
			f = 8
		}
		f = f - 1
	}

	// Add `l` in a 64 bit block in `pbs`
	l64 := uint64(l)
	b64 := make([]byte, 8) // last 64-bits
	for i := 7; i >= 0; i-- {
		// Get 8 last bits.
		b64[i] = byte(l64 & 0xFF)

		// Get rid of the last 8 bits.
		l64 = l64 >> 8
	}
	pbs = append(pbs, b64...)

	return pbs
}

func (s *Sha1) Init(hvs []uint32) {
	// Set Initial Hash Values.
	h := make([]uint32, 5)
	if len(hvs) == 5 {
		copy(h, hvs)
		s.hvs = h
	} else {
		copy(h, sha1IHashValues)
		s.hvs = h
	}
}

func (s *Sha1) Message(m []byte) {
	s.Msg = m
	s.MsgLen = len(m)
}

// SHA-1 - Pad message such that its length is a multiple of 512.
func (s *Sha1) Pad() []byte {
	// Initialize padded message
	pm := make([]byte, len(s.Msg))
	copy(pm, s.Msg)

	// Add padding.
	pm = append(pm, MDPadding(s.MsgLen)...)

	return pm
}

func (s *Sha1) Hash() []byte {
	// Pad message.
	pm := s.Pad()

	// Break into message blocks.
	mbs := shaMessageBlocks(pm)

	// Initialize hash values.
	h := make([]uint32, 5)
	copy(h, s.hvs) // Initial hash values.

	// Process each message block.
	for _, mb := range mbs {
		// Get message schedule.
		w := sha1MessageSchedule(mb)

		// Initialize working variables.
		a := h[0]
		b := h[1]
		c := h[2]
		d := h[3]
		e := h[4]

		for t := 0; t <= 79; t++ {
			tmp := shaAdd(shaRotl(a, 5), sha1FT(t, b, c, d),
				e, sha1KT(t), w[t])
			e = d
			d = c
			c = shaRotl(b, 30)
			b = a
			a = tmp
		}

		// Compute intermediate hash values.
		h[0] = shaAdd(a, h[0])
		h[1] = shaAdd(b, h[1])
		h[2] = shaAdd(c, h[2])
		h[3] = shaAdd(d, h[3])
		h[4] = shaAdd(e, h[4])
	}

	// Slurp sha1 digest from hash values.
	d := make([]byte, 0) // sha1 digest
	for i := 0; i < 5; i++ {
		// Break 32-bit hash value into 4 bytes.
		hb := make([]byte, 4)
		for j := 3; j >= 0; j-- {
			// Get last 8 bits.
			hb[j] = byte(h[i] & 0xFF)

			// Get rid of last 8 bits.
			h[i] = h[i] >> 8
		}
		d = append(d, hb...)
	}

	return d
}

func (s *Sha1) Mac(secret, msg []byte) []byte {
	s.Message(append(secret, msg...))
	return s.Hash()
}

func (s *Sha1) MacVerify(secret, msg, mac []byte) bool {
	s.Message(append(secret, msg...))
	if BytesEqual(s.Hash(), mac) {
		return true
	}
	return false
}

// HMAC-SHA1.
func HmacSha1(key, msg []byte) []byte {
	// Initialize SHA-1 object.
	sha1 := Sha1{}
	sha1.Init([]uint32{})

	// Modify key based on it's size.
	if len(key) > 64 { // > blocksize (64 bytes)
		sha1.Message(key)
		key = sha1.Hash()
	}
	if len(key) < 64 { // < blocksize (64 bytes)
		// Pad with zeroes up to 64 bytes.
		key = append(key, make([]byte, 64-len(key))...)
	}

	// Outer padded key.
	opk := FixedXORBytes(key, FillBytes(0x5c, 64))

	// Inner padded key.
	ipk := FixedXORBytes(key, FillBytes(0x36, 64))

	return sha1.Mac(opk, sha1.Mac(ipk, msg))
}