-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[IMPLEMENTATION] DSA algorithm implementation (#737)
Co-authored-by: Rak Laptudirm <[email protected]>
- Loading branch information
1 parent
ff32def
commit b9f7d55
Showing
2 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} | ||
} |