diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..99bafd7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +--- +language: go +go: + - tip diff --git a/README.md b/README.md index b49eb2c..97a27a1 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,29 @@ elgamal ======= Pure elgamal cryptosystem implemented in Go + +[![travis](https://travis-ci.org/didiercrunch/elgamal.svg)](https://travis-ci.org/didiercrunch/elgamal/) + + +#### introduction + +Implementation of the El Gamal cryptosystem. See +http://en.wikipedia.org/wiki/ElGamal_encryption for an introduction. + +The difference between this implementation and the one at golang.org/x/crypto/openpgp/elgamal +is the use of absolutly no padding here. + +The absance of padding is cool if you want to encrypt numbers and need to make algebra +on the cypher texts. The use of padding is needed when you encrypt data and you +do not want to allow algebraic operations on the cypher texts. + +Another important feature of the library is the ease of serilisation of all +the structs in JSON and BSON. + + +#### caveat + +As any cryptographic library, if you use it, you need to *trust* it. Unfortunatly, +this library as not been verified by a third party. There might be some bugs and +vulnerabilities. If you find one, please fill a bug. + diff --git a/elgamal.go b/elgamal.go new file mode 100644 index 0000000..383ad66 --- /dev/null +++ b/elgamal.go @@ -0,0 +1,133 @@ +package elgamal + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + + "gopkg.in/mgo.v2/bson" +) + +// PublicKey represents an ElGamal public key. +type PublicKey struct { + G, P, Y *big.Int +} + +func (pub *PublicKey) Encrypt(msg []byte, random io.Reader) (cypher *Cypher, err error) { + m := new(big.Int).SetBytes(msg) + + k, err := rand.Int(random, pub.P) + if err != nil { + return + } + + c1 := new(big.Int).Exp(pub.G, k, pub.P) + s := new(big.Int).Exp(pub.Y, k, pub.P) + + c2 := s.Mul(s, m) + c2.Mod(c2, pub.P) + cypher = &Cypher{c1, c2, pub.P} + return +} + +func (this *PublicKey) GetBSON() (interface{}, error) { + return bson.D{ + {"g", fmt.Sprintf("%x", this.G)}, + {"p", fmt.Sprintf("%x", this.P)}, + {"y", fmt.Sprintf("%x", this.Y)}, + }, nil +} + +// PrivateKey represents an ElGamal private key. +type PrivateKey struct { + PublicKey + X *big.Int +} + +func (this *PrivateKey) GetBSON() (interface{}, error) { + return bson.D{ + {"x", fmt.Sprintf("%x", this.X)}, + {"g", fmt.Sprintf("%x", this.G)}, + {"p", fmt.Sprintf("%x", this.P)}, + {"y", fmt.Sprintf("%x", this.Y)}, + }, nil +} + +func (priv *PrivateKey) Decrypt(cypher *Cypher) (msg []byte) { + s := new(big.Int).Exp(cypher.C1, priv.X, priv.P) + s.ModInverse(s, priv.P) + s.Mul(s, cypher.C2) + s.Mod(s, priv.P) + em := s.Bytes() + return em +} + +type Cypher struct { + C1, C2, P *big.Int +} + +func (this *Cypher) GetBSON() (interface{}, error) { + return bson.D{ + {"c1", fmt.Sprintf("%x", this.C1)}, + {"c2", fmt.Sprintf("%x", this.C2)}, + {"p", fmt.Sprintf("%x", this.P)}, + }, nil +} + +func (this *Cypher) Mul(cypher1, cypher2 *Cypher) *Cypher { + this.C1 = new(big.Int).Mod(new(big.Int).Mul(cypher1.C1, cypher2.C1), cypher1.P) + this.C2 = new(big.Int).Mod(new(big.Int).Mul(cypher1.C2, cypher2.C2), cypher1.P) + this.P = cypher1.P + return this +} + +func (this *Cypher) ToJSON() map[string]string { + return map[string]string{ + "C1": fmt.Sprintf("%x", this.C1), + "C2": fmt.Sprintf("%x", this.C2), + "P": fmt.Sprintf("%x", this.P), + } +} + +func (this *Cypher) MarshalJSON() ([]byte, error) { + return json.Marshal(this.ToJSON()) +} + +func (this *Cypher) UnmarshalJSON(bytes []byte) error { + m := make(map[string]string) + err := json.Unmarshal(bytes, &m) + if err != nil { + return err + } + this.FromJSON(m) + return nil +} + +func (this *Cypher) FromJSON(json map[string]string) (*Cypher, error) { + var err error + this.C1, err = fromHex(json["C1"]) + if err != nil { + return nil, err + } + this.C2, err = fromHex(json["C2"]) + if err != nil { + return nil, err + } + this.P, err = fromHex(json["P"]) + if err != nil { + return nil, err + } + return this, err +} + +func fromHex(hex string) (*big.Int, error) { + n, err := new(big.Int).SetString(hex, 16) + if !err { + msg := fmt.Sprintf("Cannot convert %s to int as hexadecimal", hex) + return nil, errors.New(msg) + } + return n, nil +} diff --git a/elgamal_test.go b/elgamal_test.go new file mode 100644 index 0000000..2cbca96 --- /dev/null +++ b/elgamal_test.go @@ -0,0 +1,159 @@ +package elgamal + +import ( + "bytes" + "crypto/rand" + "math/big" + "reflect" + "testing" + + "bitbucket.org/ustraca/crypto" +) + +// This is the 1024-bit MODP group from RFC 5114, section 2.1: +const primeHex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D" + + "23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACC" + + "BDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC" + + "8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371" + +const generatorHex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CF" + + "F14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F" + + "4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D" + + "18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5" + +func fromSafeHex(s string) *big.Int { + ret, err := crypto.FromHex(s) + if err != nil { + panic(err) + } + return ret +} + +func TestEncryptDecrypt(t *testing.T) { + priv := &PrivateKey{ + PublicKey: PublicKey{ + G: fromSafeHex(generatorHex), + + P: fromSafeHex(primeHex), + }, + X: fromSafeHex("42"), + } + priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P) + + message := []byte("hello world") + cypher, err := priv.PublicKey.Encrypt(message, rand.Reader) + if err != nil { + t.Errorf("error encrypting: %s", err) + } + message2 := priv.Decrypt(cypher) + if !bytes.Equal(message2, message) { + t.Errorf("decryption failed, got: '%x', want: '%x'", message2, message) + } +} + +func TestEncryptDecryptWithSmallNumbers(t *testing.T) { + priv := &PrivateKey{ + PublicKey: PublicKey{ + G: big.NewInt(33), + P: big.NewInt(71), + }, + X: big.NewInt(42), + } + priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P) + + message := big.NewInt(15).Bytes() + cypher, err := priv.PublicKey.Encrypt(message, rand.Reader) + if err != nil { + t.Errorf("error encrypting: %s", err) + } + message2 := priv.Decrypt(cypher) + if !bytes.Equal(message2, message) { + t.Errorf("decryption failed, got: %x, want: %x", message2, message) + } + +} + +func TestHomomorphicProperties(t *testing.T) { + priv := PrivateKey{ + PublicKey: PublicKey{ + G: big.NewInt(33), + P: big.NewInt(71), + }, + X: big.NewInt(42), + } + priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P) + + message1 := big.NewInt(11).Bytes() + cypher1, _ := priv.PublicKey.Encrypt(message1, rand.Reader) + + message2 := big.NewInt(12).Bytes() + cypher2, _ := priv.PublicKey.Encrypt(message2, rand.Reader) + + decyphered := priv.Decrypt(new(Cypher).Mul(cypher1, cypher2)) + expected := big.NewInt((12 * 11) % 71).Bytes() + if !bytes.Equal(expected, decyphered) { + t.Errorf("decryption failed, got: %x, want: %x", decyphered, expected) + } + +} + +func TestCypherToJSON(t *testing.T) { + cypher := &Cypher{ + C1: big.NewInt(33), + C2: big.NewInt(99), + P: big.NewInt(192), + } + exp := make(map[string]string) + exp["C1"] = "21" + exp["C2"] = "63" + exp["P"] = "c0" + + if !reflect.DeepEqual(exp, cypher.ToJSON()) { + t.Error(exp, "is not ", cypher.ToJSON()) + } +} + +func TestCypherFromJSON(t *testing.T) { + m := make(map[string]string) + m["C1"] = "21" + m["C2"] = "63" + m["P"] = "c0" + + cypher, err := new(Cypher).FromJSON(m) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(cypher.P, big.NewInt(192)) { + t.Error("P not okay") + } + if !reflect.DeepEqual(cypher.C1, big.NewInt(33)) { + t.Error("C1 not okay") + } + if !reflect.DeepEqual(cypher.C2, big.NewInt(99)) { + t.Error("C2 not okay") + } + +} + +func TestMarshalling(t *testing.T) { + cypher := &Cypher{ + C1: big.NewInt(33), + C2: big.NewInt(99), + P: big.NewInt(192), + } + s, err := cypher.MarshalJSON() + if err != nil { + t.Fail() + } + + cypher2 := new(Cypher) + err = cypher2.UnmarshalJSON(s) + if err != nil { + t.Error("could not unmarshal", err) + } + + if !reflect.DeepEqual(cypher, cypher2) { + t.Error("the two objects do not match!") + } + +}