201 lines
6.6 KiB
Go
201 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"flag"
|
|
"fmt"
|
|
)
|
|
|
|
var verbose bool
|
|
|
|
func main() {
|
|
flag.BoolVar(&verbose, "verbose", false, "verbose")
|
|
flag.Parse()
|
|
|
|
// 256 bits secretKey
|
|
// in real life it should be generated using crypto.Rand
|
|
secretKey := []byte("secretsecretsecretsecretsecretse")
|
|
|
|
legitimateData := []byte("user_id=1&role=user")
|
|
legitimateSignature := sign(secretKey, legitimateData)
|
|
|
|
// sha256 := append(make([]byte, 0), secretKey...)
|
|
// // sha256 := make([]byte, 0)
|
|
// sha256 = append(sha256, legitimateData...)
|
|
// oadding := generatePadding(uint64(len(secretKey)), uint64(len(legitimateData)))
|
|
// sha256 = append(sha256, oadding...)
|
|
// fmt.Println(hex.Dump(sha256))
|
|
|
|
fmt.Printf("SecretKey: %s\n", hex.EncodeToString(secretKey))
|
|
fmt.Printf("Legitimate Data: %s\n", string(legitimateData))
|
|
fmt.Printf("Legitimate Signature SHA256(SecretKey || LegitimateData): %s\n", hex.EncodeToString(legitimateSignature))
|
|
fmt.Printf("Verify LegitimateSignature == SHA256(SecretKey || LegitimateData): %v\n", verifySignature(secretKey, legitimateSignature, legitimateData))
|
|
|
|
fmt.Println("\n---------------------------------------------------------------------------------------------------\n")
|
|
|
|
maliciousData := []byte("&something=true&role=admin")
|
|
maliciousMessage := generateMaliciousMessage(uint64(len(secretKey)), legitimateData, maliciousData)
|
|
maliciousSignature := forgeSignature(legitimateSignature, maliciousData, uint64(len(secretKey)+len(legitimateData)))
|
|
|
|
fmt.Printf("Malicious Data: %s\n", string(maliciousData))
|
|
if verbose {
|
|
fmt.Println("Malicious Message (LegitimateData || padding || MaliciousData):")
|
|
fmt.Println(hex.Dump(maliciousMessage))
|
|
}
|
|
fmt.Printf("Malicious Signature: %s\n", hex.EncodeToString(maliciousSignature))
|
|
fmt.Printf("Verify MaliciousSignature == SHA256(SecretKey, MaliciousMessage): %v\n", verifySignature(secretKey, maliciousSignature, maliciousMessage))
|
|
}
|
|
|
|
// forgeSignature performs a length extension attack by loading a SHA256 hash from the legitimate signature
|
|
// and appending the malicious data.
|
|
func forgeSignature(legitimateSignature []byte, maliciousData []byte, secretKeyAndDataLength uint64) (forgedSignature []byte) {
|
|
digest := loadSha256(legitimateSignature, secretKeyAndDataLength)
|
|
|
|
digest.Write(maliciousData)
|
|
hash := digest.Sum(nil)
|
|
forgedSignature = hash[:]
|
|
|
|
return
|
|
}
|
|
|
|
// generateMaliciousMessage generates the malicious message used to forge a signature without knowing the
|
|
// secretKey. The message has the following format: (legitimateData || padding || maliciousData)
|
|
func generateMaliciousMessage(secretKeyLength uint64, legitimateData []byte, maliciousData []byte) (message []byte) {
|
|
padding := generatePadding(secretKeyLength + uint64(len(legitimateData)))
|
|
message = make([]byte, 0, len(legitimateData)+len(padding)+len(maliciousData))
|
|
|
|
message = append(message, legitimateData...)
|
|
message = append(message, padding...)
|
|
message = append(message, maliciousData...)
|
|
|
|
return
|
|
}
|
|
|
|
// generatePadding generates the required padding to fill SHA256 blocks of 512 bits (64 bytes)
|
|
// with (secretKey || data || padding)
|
|
// The padding format is defined in RFC6234: https://www.rfc-editor.org/rfc/rfc6234#page-8
|
|
// inspired by `sha256.go`
|
|
func generatePadding(secretKeyAndDataLength uint64) []byte {
|
|
var tmp [64 + 8]byte // padding + length buffer
|
|
var t uint64
|
|
|
|
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
|
tmp[0] = 0x80
|
|
if secretKeyAndDataLength%64 < 56 {
|
|
t = 56 - secretKeyAndDataLength%64
|
|
} else {
|
|
t = 64 + 56 - secretKeyAndDataLength%64
|
|
}
|
|
|
|
// Length in bits.
|
|
secretKeyAndDataLength <<= 3
|
|
padlen := tmp[:t+8]
|
|
binary.BigEndian.PutUint64(padlen[t+0:], secretKeyAndDataLength)
|
|
|
|
return padlen
|
|
}
|
|
|
|
// verifySignature verifies that Signature == SHA256(secretKey || data)
|
|
func verifySignature(secretKey []byte, signatureToVerify []byte, data []byte) (isValid bool) {
|
|
isValid = false
|
|
signature := sign(secretKey, data)
|
|
|
|
if subtle.ConstantTimeCompare(signature, signatureToVerify) == 1 {
|
|
isValid = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// sign generates a SHA256 MAC such as SHA256(secretKey || data)
|
|
func sign(secretKey []byte, data []byte) (signature []byte) {
|
|
hasher := sha256.New()
|
|
|
|
hasher.Write(secretKey)
|
|
hasher.Write(data)
|
|
hash := hasher.Sum(nil)
|
|
signature = hash[:]
|
|
|
|
return
|
|
}
|
|
|
|
// loadSha256 is a slightly modified version of digest.UnmarshalBinary in order to load the state from a
|
|
// normal SHA256 hash instead of the "proprietary version" generated by digest.MarshalBinary
|
|
func loadSha256(hashBytes []byte, secretKeyAndDataLength uint64) (hash *digest) {
|
|
if len(hashBytes) != sha256.Size {
|
|
panic("loadSha256: not a valid SHA256 hash")
|
|
}
|
|
|
|
hash = new(digest)
|
|
hash.Reset()
|
|
|
|
hashBytes, hash.h[0] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[1] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[2] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[3] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[4] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[5] = consumeUint32(hashBytes)
|
|
hashBytes, hash.h[6] = consumeUint32(hashBytes)
|
|
_, hash.h[7] = consumeUint32(hashBytes)
|
|
// hash.len is the nearest upper multiple of 64 of the hashed data (secretKeyAndDataLength)
|
|
// hash.len = secretKeyAndDataLength + 64 - (secretKeyAndDataLength % 64)
|
|
// hash.nx = int(hash.len % chunk)
|
|
// hash.len is the length of consumed bytes, including the paddings
|
|
hash.len = secretKeyAndDataLength + uint64(len(generatePadding(secretKeyAndDataLength)))
|
|
|
|
return
|
|
}
|
|
|
|
// func signBinary(secretKey []byte, data []byte) (signature []byte) {
|
|
// hasher := new(digest)
|
|
// hasher.Reset()
|
|
|
|
// hasher.Write(secretKey)
|
|
// hasher.Write(data)
|
|
// hash := hasher.checkSum()
|
|
// signature = hash[:]
|
|
|
|
// if verbose {
|
|
// binary, _ := hasher.MarshalBinary()
|
|
// fmt.Println("SHA256 Binary:")
|
|
// fmt.Println(hex.Dump(binary))
|
|
// }
|
|
// return
|
|
// }
|
|
|
|
// func loadSha256Binary(hashBytes []byte, secretKeyAndDataLength uint64) (hash *digest) {
|
|
// digestBinary := make([]byte, 0, marshaledSize)
|
|
// digestBinary = append(digestBinary, []byte(magic256)...)
|
|
// digestBinary = append(digestBinary, hashBytes...)
|
|
// digestBinary = append(digestBinary, make([]byte, chunk)...)
|
|
// digestBinary = binary.BigEndian.AppendUint64(digestBinary, secretKeyAndDataLength+64-(secretKeyAndDataLength%64))
|
|
|
|
// hash = new(digest)
|
|
// hash.Reset()
|
|
// err := hash.UnmarshalBinary(digestBinary)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
|
|
// if verbose {
|
|
// fmt.Println("SHA256 state:")
|
|
// fmt.Println(hex.Dump(digestBinary))
|
|
// }
|
|
|
|
// return
|
|
// }
|
|
|
|
// dumpBinary prints 00000000 00000000 00000000 00000001
|
|
// func dumpBinary(data []byte) {
|
|
// for i, n := range data {
|
|
// fmt.Printf("%08b ", n)
|
|
// if (i+1)%4 == 0 && i != 0 {
|
|
// fmt.Println("")
|
|
// }
|
|
// }
|
|
// fmt.Println("")
|
|
// }
|