Skip to content

Commit b9f7d55

Browse files
[IMPLEMENTATION] DSA algorithm implementation (#737)
Co-authored-by: Rak Laptudirm <[email protected]>
1 parent ff32def commit b9f7d55

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed

cipher/dsa/dsa.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
dsa.go
3+
description: DSA encryption and decryption including key generation
4+
details: [DSA wiki](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm)
5+
author(s): [ddaniel27](https://github.com/ddaniel27)
6+
*/
7+
package dsa
8+
9+
import (
10+
"crypto/rand"
11+
"io"
12+
"math/big"
13+
)
14+
15+
const (
16+
numMRTests = 64 // Number of Miller-Rabin tests
17+
L = 1024 // Number of bits in p
18+
N = 160 // Number of bits in q
19+
)
20+
21+
type (
22+
// parameters represents the DSA parameters
23+
parameters struct {
24+
P, Q, G *big.Int
25+
}
26+
27+
// dsa represents the DSA
28+
dsa struct {
29+
parameters
30+
pubKey *big.Int // public key (y)
31+
privKey *big.Int // private key (x)
32+
}
33+
)
34+
35+
// New creates a new DSA instance
36+
func New() *dsa {
37+
d := new(dsa)
38+
d.dsaParameterGeneration()
39+
d.keyGen()
40+
return d
41+
}
42+
43+
// Parameter generation for DSA
44+
// 1. FIPS 186-4 specifies that the L and N values must be (1024, 160), (2048, 224), or (3072, 256)
45+
// 2. Choose a N-bit prime q
46+
// 3. Choose a L-bit prime p such that p-1 is a multiple of q
47+
// 4. Choose an integer h randomly from the range [2, p-2]
48+
// 5. Compute g = h^((p-1)/q) mod p
49+
// 6. Return (p, q, g)
50+
func (dsa *dsa) dsaParameterGeneration() {
51+
var err error
52+
p, q, bigInt := new(big.Int), new(big.Int), new(big.Int)
53+
one, g, h := big.NewInt(1), big.NewInt(1), big.NewInt(2)
54+
pBytes := make([]byte, L/8)
55+
56+
// GPLoop is a label for the loop
57+
// We use this loop to change the prime q if we don't find a prime p
58+
GPLoop:
59+
for {
60+
// 2. Choose a N-bit prime q
61+
q, err = rand.Prime(rand.Reader, N)
62+
if err != nil {
63+
panic(err)
64+
}
65+
66+
for i := 0; i < 4*L; i++ {
67+
// 3. Choose a L-bit prime p such that p-1 is a multiple of q
68+
// In this case we generate a random number of L bits
69+
if _, err := io.ReadFull(rand.Reader, pBytes); err != nil {
70+
panic(err)
71+
}
72+
73+
// This are the minimum conditions for p being a possible prime
74+
pBytes[len(pBytes)-1] |= 1 // p is odd
75+
pBytes[0] |= 0x80 // p has the highest bit set
76+
p.SetBytes(pBytes)
77+
78+
// Instead of using (p-1)%q == 0
79+
// We ensure that p-1 is a multiple of q and validates if p is prime
80+
bigInt.Mod(p, q)
81+
bigInt.Sub(bigInt, one)
82+
p.Sub(p, bigInt)
83+
if p.BitLen() < L || !p.ProbablyPrime(numMRTests) { // Check if p is prime and has L bits
84+
continue
85+
}
86+
87+
dsa.P = p
88+
dsa.Q = q
89+
break GPLoop
90+
}
91+
}
92+
93+
// 4. Choose an integer h randomly from the range [2, p-2]. Commonly, h = 2
94+
// 5. Compute g = h^((p-1)/q) mod p. In case g == 1, increment h until g != 1
95+
pm1 := new(big.Int).Sub(p, one)
96+
97+
for g.Cmp(one) == 0 {
98+
g.Exp(h, new(big.Int).Div(pm1, q), p)
99+
h.Add(h, one)
100+
}
101+
102+
dsa.G = g
103+
}
104+
105+
// keyGen is key generation for DSA
106+
// 1. Choose a random integer x from the range [1, q-1]
107+
// 2. Compute y = g^x mod p
108+
func (dsa *dsa) keyGen() {
109+
// 1. Choose a random integer x from the range [1, q-1]
110+
x, err := rand.Int(rand.Reader, new(big.Int).Sub(dsa.Q, big.NewInt(1)))
111+
if err != nil {
112+
panic(err)
113+
}
114+
115+
dsa.privKey = x
116+
117+
// 2. Compute y = g^x mod p
118+
dsa.pubKey = new(big.Int).Exp(dsa.G, x, dsa.P)
119+
}
120+
121+
// Sign is signature generation for DSA
122+
// 1. Choose a random integer k from the range [1, q-1]
123+
// 2. Compute r = (g^k mod p) mod q
124+
// 3. Compute s = (k^-1 * (H(m) + x*r)) mod q
125+
func Sign(m []byte, p, q, g, x *big.Int) (r, s *big.Int) {
126+
// 1. Choose a random integer k from the range [1, q-1]
127+
k, err := rand.Int(rand.Reader, new(big.Int).Sub(q, big.NewInt(1)))
128+
if err != nil {
129+
panic(err)
130+
}
131+
132+
// 2. Compute r = (g^k mod p) mod q
133+
r = new(big.Int).Exp(g, k, p)
134+
r.Mod(r, q)
135+
136+
// 3. Compute s = (k^-1 * (H(m) + x*r)) mod q
137+
h := new(big.Int).SetBytes(m) // This should be the hash of the message
138+
s = new(big.Int).ModInverse(k, q) // k^-1 mod q
139+
s.Mul(
140+
s,
141+
new(big.Int).Add( // (H(m) + x*r)
142+
h,
143+
new(big.Int).Mul(x, r),
144+
),
145+
)
146+
s.Mod(s, q) // mod q
147+
148+
return r, s
149+
}
150+
151+
// Verify is signature verification for DSA
152+
// 1. Compute w = s^-1 mod q
153+
// 2. Compute u1 = (H(m) * w) mod q
154+
// 3. Compute u2 = (r * w) mod q
155+
// 4. Compute v = ((g^u1 * y^u2) mod p) mod q
156+
// 5. If v == r, the signature is valid
157+
func Verify(m []byte, r, s, p, q, g, y *big.Int) bool {
158+
// 1. Compute w = s^-1 mod q
159+
w := new(big.Int).ModInverse(s, q)
160+
161+
// 2. Compute u1 = (H(m) * w) mod q
162+
h := new(big.Int).SetBytes(m) // This should be the hash of the message
163+
u1 := new(big.Int).Mul(h, w)
164+
u1.Mod(u1, q)
165+
166+
// 3. Compute u2 = (r * w) mod q
167+
u2 := new(big.Int).Mul(r, w)
168+
u2.Mod(u2, q)
169+
170+
// 4. Compute v = ((g^u1 * y^u2) mod p) mod q
171+
v := new(big.Int).Exp(g, u1, p)
172+
v.Mul(
173+
v,
174+
new(big.Int).Exp(y, u2, p),
175+
)
176+
v.Mod(v, p)
177+
v.Mod(v, q)
178+
179+
// 5. If v == r, the signature is valid
180+
return v.Cmp(r) == 0
181+
}
182+
183+
// GetPublicKey returns the public key (y)
184+
func (dsa *dsa) GetPublicKey() *big.Int {
185+
return dsa.pubKey
186+
}
187+
188+
// GetParameters returns the DSA parameters (p, q, g)
189+
func (dsa *dsa) GetParameters() parameters {
190+
return dsa.parameters
191+
}
192+
193+
// GetPrivateKey returns the private Key (x)
194+
func (dsa *dsa) GetPrivateKey() *big.Int {
195+
return dsa.privKey
196+
}

cipher/dsa/dsa_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package dsa_test
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
"github.com/TheAlgorithms/Go/cipher/dsa"
8+
)
9+
10+
func TestDSA(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
message string
14+
alter bool
15+
want bool
16+
}{
17+
{
18+
name: "valid signature",
19+
message: "Hello, world!",
20+
alter: false,
21+
want: true,
22+
},
23+
{
24+
name: "invalid signature",
25+
message: "Hello, world!",
26+
alter: true,
27+
want: false,
28+
},
29+
}
30+
// Generate a DSA key pair
31+
dsaInstance := dsa.New()
32+
pubKey := dsaInstance.GetPublicKey()
33+
params := dsaInstance.GetParameters()
34+
privKey := dsaInstance.GetPrivateKey()
35+
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
39+
// Sign the message
40+
r, s := dsa.Sign(
41+
[]byte(tt.message),
42+
params.P,
43+
params.Q,
44+
params.G,
45+
privKey,
46+
)
47+
48+
if tt.alter {
49+
// Alter the signature
50+
r.Add(r, big.NewInt(1))
51+
}
52+
53+
// Verify the signature
54+
if got := dsa.Verify(
55+
[]byte(tt.message),
56+
r,
57+
s,
58+
params.P,
59+
params.Q,
60+
params.G,
61+
pubKey,
62+
); got != tt.want {
63+
t.Errorf("got %v, want %v", got, tt.want)
64+
}
65+
})
66+
}
67+
}
68+
69+
/* ------------------- BENCHMARKS ------------------- */
70+
func BenchmarkDSANew(b *testing.B) {
71+
for i := 0; i < b.N; i++ {
72+
dsa.New()
73+
}
74+
}
75+
76+
func BenchmarkDSASign(b *testing.B) {
77+
dsaInstance := dsa.New()
78+
params := dsaInstance.GetParameters()
79+
privKey := dsaInstance.GetPrivateKey()
80+
for i := 0; i < b.N; i++ {
81+
dsa.Sign(
82+
[]byte("Hello, World!"),
83+
params.P,
84+
params.Q,
85+
params.G,
86+
privKey,
87+
)
88+
}
89+
}
90+
91+
func BenchmarkDSAVerify(b *testing.B) {
92+
dsaInstance := dsa.New()
93+
pubKey := dsaInstance.GetPublicKey()
94+
params := dsaInstance.GetParameters()
95+
privKey := dsaInstance.GetPrivateKey()
96+
r, s := dsa.Sign(
97+
[]byte("Hello, World!"),
98+
params.P,
99+
params.Q,
100+
params.G,
101+
privKey,
102+
)
103+
for i := 0; i < b.N; i++ {
104+
dsa.Verify(
105+
[]byte("Hello, World!"),
106+
r,
107+
s,
108+
params.P,
109+
params.Q,
110+
params.G,
111+
pubKey,
112+
)
113+
}
114+
}

0 commit comments

Comments
 (0)