Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions cipher/dsa/dsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
dsa.go
description: DSA encryption and decryption including key generation
details: [DSA wiki](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm)
author(s): [ddaniel27](https://github.com/ddaniel27)
*/
package dsa

import (
"crypto/rand"
"io"
"math/big"
)

const (
numMRTests = 64 // Number of Miller-Rabin tests
L = 1024 // Number of bits in p
N = 160 // Number of bits in q
)

type (
// parameters represents the DSA parameters
parameters struct {
P, Q, G *big.Int
}

// dsa represents the DSA
dsa struct {
parameters
pubKey *big.Int // public key (y)
privKey *big.Int // private key (x)
}
)

// New creates a new DSA instance
func New() *dsa {
d := new(dsa)
d.dsaParameterGeneration()
d.keyGen()
return d
}

// Parameter generation for DSA
// 1. FIPS 186-4 specifies that the L and N values must be (1024, 160), (2048, 224), or (3072, 256)
// 2. Choose a N-bit prime q
// 3. Choose a L-bit prime p such that p-1 is a multiple of q
// 4. Choose an integer h randomly from the range [2, p-2]
// 5. Compute g = h^((p-1)/q) mod p
// 6. Return (p, q, g)
func (dsa *dsa) dsaParameterGeneration() {
var err error
p, q, bigInt := new(big.Int), new(big.Int), new(big.Int)
one, g, h := big.NewInt(1), big.NewInt(1), big.NewInt(2)
pBytes := make([]byte, L/8)

// GPLoop is a label for the loop
// We use this loop to change the prime q if we don't find a prime p
GPLoop:
for {
// 2. Choose a N-bit prime q
q, err = rand.Prime(rand.Reader, N)
if err != nil {
panic(err)
}

for i := 0; i < 4*L; i++ {
// 3. Choose a L-bit prime p such that p-1 is a multiple of q
// In this case we generate a random number of L bits
if _, err := io.ReadFull(rand.Reader, pBytes); err != nil {
panic(err)
}

// This are the minimum conditions for p being a possible prime
pBytes[len(pBytes)-1] |= 1 // p is odd
pBytes[0] |= 0x80 // p has the highest bit set
p.SetBytes(pBytes)

// Instead of using (p-1)%q == 0
// We ensure that p-1 is a multiple of q and validates if p is prime
bigInt.Mod(p, q)
bigInt.Sub(bigInt, one)
p.Sub(p, bigInt)
if p.BitLen() < L || !p.ProbablyPrime(numMRTests) { // Check if p is prime and has L bits
continue
}

dsa.P = p
dsa.Q = q
break GPLoop
}
}

// 4. Choose an integer h randomly from the range [2, p-2]. Commonly, h = 2
// 5. Compute g = h^((p-1)/q) mod p. In case g == 1, increment h until g != 1
pm1 := new(big.Int).Sub(p, one)

for g.Cmp(one) == 0 {
g.Exp(h, new(big.Int).Div(pm1, q), p)
h.Add(h, one)
}

dsa.G = g
}

// keyGen is key generation for DSA
// 1. Choose a random integer x from the range [1, q-1]
// 2. Compute y = g^x mod p
func (dsa *dsa) keyGen() {
// 1. Choose a random integer x from the range [1, q-1]
x, err := rand.Int(rand.Reader, new(big.Int).Sub(dsa.Q, big.NewInt(1)))
if err != nil {
panic(err)
}

dsa.privKey = x

// 2. Compute y = g^x mod p
dsa.pubKey = new(big.Int).Exp(dsa.G, x, dsa.P)
}

// Sign is signature generation for DSA
// 1. Choose a random integer k from the range [1, q-1]
// 2. Compute r = (g^k mod p) mod q
// 3. Compute s = (k^-1 * (H(m) + x*r)) mod q
func Sign(m []byte, p, q, g, x *big.Int) (r, s *big.Int) {
// 1. Choose a random integer k from the range [1, q-1]
k, err := rand.Int(rand.Reader, new(big.Int).Sub(q, big.NewInt(1)))
if err != nil {
panic(err)
}

// 2. Compute r = (g^k mod p) mod q
r = new(big.Int).Exp(g, k, p)
r.Mod(r, q)

// 3. Compute s = (k^-1 * (H(m) + x*r)) mod q
h := new(big.Int).SetBytes(m) // This should be the hash of the message
s = new(big.Int).ModInverse(k, q) // k^-1 mod q
s.Mul(
s,
new(big.Int).Add( // (H(m) + x*r)
h,
new(big.Int).Mul(x, r),
),
)
s.Mod(s, q) // mod q

return r, s
}

// Verify is signature verification for DSA
// 1. Compute w = s^-1 mod q
// 2. Compute u1 = (H(m) * w) mod q
// 3. Compute u2 = (r * w) mod q
// 4. Compute v = ((g^u1 * y^u2) mod p) mod q
// 5. If v == r, the signature is valid
func Verify(m []byte, r, s, p, q, g, y *big.Int) bool {
// 1. Compute w = s^-1 mod q
w := new(big.Int).ModInverse(s, q)

// 2. Compute u1 = (H(m) * w) mod q
h := new(big.Int).SetBytes(m) // This should be the hash of the message
u1 := new(big.Int).Mul(h, w)
u1.Mod(u1, q)

// 3. Compute u2 = (r * w) mod q
u2 := new(big.Int).Mul(r, w)
u2.Mod(u2, q)

// 4. Compute v = ((g^u1 * y^u2) mod p) mod q
v := new(big.Int).Exp(g, u1, p)
v.Mul(
v,
new(big.Int).Exp(y, u2, p),
)
v.Mod(v, p)
v.Mod(v, q)

// 5. If v == r, the signature is valid
return v.Cmp(r) == 0
}

// GetPublicKey returns the public key (y)
func (dsa *dsa) GetPublicKey() *big.Int {
return dsa.pubKey
}

// GetParameters returns the DSA parameters (p, q, g)
func (dsa *dsa) GetParameters() parameters {
return dsa.parameters
}

// GetPrivateKey returns the private Key (x)
func (dsa *dsa) GetPrivateKey() *big.Int {
return dsa.privKey
}
114 changes: 114 additions & 0 deletions cipher/dsa/dsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dsa_test

import (
"math/big"
"testing"

"github.com/TheAlgorithms/Go/cipher/dsa"
)

func TestDSA(t *testing.T) {
tests := []struct {
name string
message string
alter bool
want bool
}{
{
name: "valid signature",
message: "Hello, world!",
alter: false,
want: true,
},
{
name: "invalid signature",
message: "Hello, world!",
alter: true,
want: false,
},
}
// Generate a DSA key pair
dsaInstance := dsa.New()
pubKey := dsaInstance.GetPublicKey()
params := dsaInstance.GetParameters()
privKey := dsaInstance.GetPrivateKey()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

// Sign the message
r, s := dsa.Sign(
[]byte(tt.message),
params.P,
params.Q,
params.G,
privKey,
)

if tt.alter {
// Alter the signature
r.Add(r, big.NewInt(1))
}

// Verify the signature
if got := dsa.Verify(
[]byte(tt.message),
r,
s,
params.P,
params.Q,
params.G,
pubKey,
); got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}

/* ------------------- BENCHMARKS ------------------- */
func BenchmarkDSANew(b *testing.B) {
for i := 0; i < b.N; i++ {
dsa.New()
}
}

func BenchmarkDSASign(b *testing.B) {
dsaInstance := dsa.New()
params := dsaInstance.GetParameters()
privKey := dsaInstance.GetPrivateKey()
for i := 0; i < b.N; i++ {
dsa.Sign(
[]byte("Hello, World!"),
params.P,
params.Q,
params.G,
privKey,
)
}
}

func BenchmarkDSAVerify(b *testing.B) {
dsaInstance := dsa.New()
pubKey := dsaInstance.GetPublicKey()
params := dsaInstance.GetParameters()
privKey := dsaInstance.GetPrivateKey()
r, s := dsa.Sign(
[]byte("Hello, World!"),
params.P,
params.Q,
params.G,
privKey,
)
for i := 0; i < b.N; i++ {
dsa.Verify(
[]byte("Hello, World!"),
r,
s,
params.P,
params.Q,
params.G,
pubKey,
)
}
}
Loading