Skip to content

Commit

Permalink
[IMPLEMENTATION] DSA algorithm implementation (#737)
Browse files Browse the repository at this point in the history
Co-authored-by: Rak Laptudirm <[email protected]>
  • Loading branch information
ddaniel27 and raklaptudirm authored Oct 23, 2024
1 parent ff32def commit b9f7d55
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
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,
)
}
}

0 comments on commit b9f7d55

Please sign in to comment.