This Go code implements a 256-bit asymmetric encryption scheme using Elliptic Curve Cryptography (ECC). It allows for encrypting and decrypting data using the NIST P-256 elliptic curve. During encryption, a random value is chosen, and the resulting point is computed. The ciphertext is formed from the coordinates of this point, along with the hash of the message. Decryption involves computing another point, from which the session key is derived, and then decrypting the ciphertext using an XOR operation. Finally, integrity is verified by comparing the hash of the recovered message with the previously received hash.
• Select a random value \( k \) from the interval \( [1, n-1] \), where \( n \) is the order of the cyclic group generated by the elliptic curve.
• Compute the point \( k \cdot G \), where \( G \) is the base point on the curve, resulting in the coordinates \( (x_1, y_1) \) of the point \( k \cdot G \).
• Compute the hash \( H(m) \) of the message \( m \).
• Derive the session key from the coordinates \( (x_2, y_2) \) of the point \( k \cdot P \), where \( P \) is the public key of the recipient, using a Key Derivation Function (KDF).
• Encrypt the message using an XOR operation with the derived session key.
• Compute the ciphertext, which consists of the coordinates \( (x_1, y_1) \), the hash \( H(m) \), and the encrypted message.
• The ciphertext can be organized in different modes, such as \( C_1C_3C_2 \) or \( C_1C_2C_3 \), depending on the chosen encryption scheme.
• Compute the point \( d \cdot (x_1, y_1) \), where \( d \) is the corresponding private key \( d \) of the recipient.
• Derive the session key from the coordinates \( (x_2, y_2) \) of the point \( d \cdot (x_1, y_1) \) using the same Key Derivation Function (KDF) as in encryption.
• Decrypt the ciphertext by performing an XOR operation between the ciphertext bytes and the session key.
• Verify the integrity by checking if the hash of the recovered message matches the received hash \( H(m) \).
It outlines the key steps involved in both encryption and decryption, including the generation of random values, computation of points on the elliptic curve, hashing of messages, construction of ciphertext, derivation of session keys, decryption process, and integrity verification.
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/sha256" "crypto/rand" "encoding/asn1" "fmt" "math/big" "errors" "bytes" ) // ecCipher represents the encrypted data structure type ecCipher struct { XCoordinate *big.Int YCoordinate *big.Int HASH []byte CipherText []byte } // GenerateKeyPair generates a public-private key pair using secp256r1 func generateKeyPair() (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { curve := elliptic.P256() // secp256r1 priv, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { return nil, nil, err } return priv, &priv.PublicKey, nil } // EncryptAsn1 encrypts data using ASN.1 encoding func EncryptAsn1(pub *ecdsa.PublicKey, data []byte) ([]byte, error) { cipher, err := Encrypt(pub, data) if err != nil { return nil, err } return CipherMarshal(cipher) } // DecryptAsn1 decrypts data using ASN.1 encoding func DecryptAsn1(priv *ecdsa.PrivateKey, data []byte) ([]byte, error) { cipher, err := CipherUnmarshal(data) if err != nil { return nil, err } return Decrypt(priv, cipher) } // Encrypt encrypts data using ECC (Elliptic Curve Cryptography) func Encrypt(pub *ecdsa.PublicKey, data []byte) (*ecCipher, error) { curve := pub.Curve k, err := rand.Int(rand.Reader, curve.Params().N) if err != nil { return nil, err } // Compute x1, y1 (ephemeral public key) and x2, y2 (shared secret) x1, y1 := curve.ScalarBaseMult(k.Bytes()) x2, y2 := curve.ScalarMult(pub.X, pub.Y, k.Bytes()) // Hash the message hash := sha256.Sum256(data) // Use x2 and y2 to derive a shared secret sharedSecret := sha256.Sum256(append(x2.Bytes(), y2.Bytes()...)) // Creating encrypted text using the shared secret (sharedSecret) ciphertext := xorWithSharedSecret(data, sharedSecret[:]) // Create the encrypted data object cipher := &ecCipher{ XCoordinate: x1, YCoordinate: y1, HASH: hash[:], CipherText: ciphertext, } return cipher, nil } // Decrypt decrypts data using ECC (Elliptic Curve Cryptography) func Decrypt(priv *ecdsa.PrivateKey, cipher *ecCipher) ([]byte, error) { curve := priv.Curve x2, y2 := curve.ScalarMult(cipher.XCoordinate, cipher.YCoordinate, priv.D.Bytes()) // Use x2 and y2 to derive the shared secret sharedSecret := sha256.Sum256(append(x2.Bytes(), y2.Bytes()...)) // Decrypt the ciphertext decrypted := xorWithSharedSecret(cipher.CipherText, sharedSecret[:]) // Verify the integrity with the hash hash := sha256.Sum256(decrypted) if bytes.Compare(cipher.HASH, hash[:]) != 0 { return nil, errors.New("Integrity check failed") } return decrypted, nil } // xorWithSharedSecret uses the shared secret (sharedSecret) to encrypt or decrypt the data func xorWithSharedSecret(data, sharedSecret []byte) []byte { result := make([]byte, len(data)) for i := 0; i < len(data); i++ { result[i] = data[i] ^ sharedSecret[i%len(sharedSecret)] } return result } // CipherMarshal encodes the data structure into ASN.1 format func CipherMarshal(cipher *ecCipher) ([]byte, error) { return asn1.Marshal(*cipher) } // CipherUnmarshal decodes data from ASN.1 format into the ecCipher structure func CipherUnmarshal(data []byte) (*ecCipher, error) { var cipher ecCipher _, err := asn1.Unmarshal(data, &cipher) if err != nil { return nil, err } return &cipher, nil } func main() { // Generate the key pair priv, pub, err := generateKeyPair() if err != nil { fmt.Println("Error generating keys:", err) return } // Example message to encrypt message := []byte("Hello, secure world!") // Encrypt the message ciphertext, err := EncryptAsn1(pub, message) if err != nil { fmt.Println("Encryption failed:", err) return } // Print the encrypted message fmt.Printf("Encrypted message (ASN.1): %x\n", ciphertext) // Decrypt the message decryptedMessage, err := DecryptAsn1(priv, ciphertext) if err != nil { fmt.Println("Decryption failed:", err) return } // Print the decrypted message fmt.Printf("Decrypted message: %s\n", string(decryptedMessage)) }
To install the library, save the following code to the path go/src/crypto/ecdsa/eccrypt.go
.
package ecdsa import ( "bytes" "crypto/sha256" "encoding/asn1" "encoding/binary" "errors" "io" "math/big" ) // ecCipher represents a structure for holding coordinates of a point // on the elliptic curve, the hash of the message, and the ciphertext. type ecCipher struct { XCoordinate *big.Int YCoordinate *big.Int HASH []byte CipherText []byte } // EncryptAsn1 encrypts data using ASN.1 encoding. func (pub *PublicKey) EncryptAsn1(data []byte, random io.Reader) ([]byte, error) { return EncryptAsn1(pub, data, random) } // DecryptAsn1 decrypts data using ASN.1 encoding. func (priv *PrivateKey) DecryptAsn1(data []byte) ([]byte, error) { return DecryptAsn1(priv, data) } // EncryptAsn1 encrypts data with ASN.1 encoding. func EncryptAsn1(pub *PublicKey, data []byte, rand io.Reader) ([]byte, error) { cipher, err := Encrypt(pub, data, rand, 0) if err != nil { return nil, err } return CipherMarshal(cipher) } // DecryptAsn1 decrypts data with ASN.1 encoding. func DecryptAsn1(pub *PrivateKey, data []byte) ([]byte, error) { cipher, err := CipherUnmarshal(data) if err != nil { return nil, err } return Decrypt(pub, cipher, 0) } // CipherMarshal encodes the cipher structure into ASN.1 format. func CipherMarshal(data []byte) ([]byte, error) { data = data[1:] x := new(big.Int).SetBytes(data[:32]) y := new(big.Int).SetBytes(data[32:64]) hash := data[64:96] cipherText := data[96:] return asn1.Marshal(ecCipher{x, y, hash, cipherText}) } // CipherUnmarshal decodes data from ASN.1 format into the cipher structure. func CipherUnmarshal(data []byte) ([]byte, error) { var cipher ecCipher _, err := asn1.Unmarshal(data, &cipher) if err != nil { return nil, err } x := cipher.XCoordinate.Bytes() y := cipher.YCoordinate.Bytes() hash := cipher.HASH cipherText := cipher.CipherText if n := len(x); n < 32 { x = append(zeroByteSlice()[:32-n], x...) } if n := len(y); n < 32 { y = append(zeroByteSlice()[:32-n], y...) } c := []byte{} c = append(c, x...) c = append(c, y...) c = append(c, hash...) c = append(c, cipherText...) return append([]byte{0x04}, c...), nil } // Encrypt encrypts data using ECC. func Encrypt(pub *PublicKey, data []byte, random io.Reader, mode int) ([]byte, error) { length := len(data) for { c := []byte{} curve := pub.Curve k, err := randFieldElement(curve, random) if err != nil { return nil, err } x1, y1 := curve.ScalarBaseMult(k.Bytes()) x2, y2 := curve.ScalarMult(pub.X, pub.Y, k.Bytes()) x1Buf := x1.Bytes() y1Buf := y1.Bytes() x2Buf := x2.Bytes() y2Buf := y2.Bytes() if n := len(x1Buf); n < 32 { x1Buf = append(zeroByteSlice()[:32-n], x1Buf...) } if n := len(y1Buf); n < 32 { y1Buf = append(zeroByteSlice()[:32-n], y1Buf...) } if n := len(x2Buf); n < 32 { x2Buf = append(zeroByteSlice()[:32-n], x2Buf...) } if n := len(y2Buf); n < 32 { y2Buf = append(zeroByteSlice()[:32-n], y2Buf...) } c = append(c, x1Buf...) c = append(c, y1Buf...) tm := []byte{} tm = append(tm, x2Buf...) tm = append(tm, data...) tm = append(tm, y2Buf...) Sum256 := func(msg []byte) []byte { res := sha256.New() res.Write(msg) hash := res.Sum(nil) return []byte(hash) } h := Sum256(tm) c = append(c, h...) ct, ok := ikdf(length, x2Buf, y2Buf) if !ok { continue } c = append(c, ct...) for i := 0; i < length; i++ { c[96+i] ^= data[i] } switch mode { case 0: return append([]byte{0x04}, c...), nil case 1: c1 := make([]byte, 64) c2 := make([]byte, len(c)-96) c3 := make([]byte, 32) copy(c1, c[:64]) copy(c3, c[64:96]) copy(c2, c[96:]) ciphertext := []byte{} ciphertext = append(ciphertext, c1...) ciphertext = append(ciphertext, c2...) ciphertext = append(ciphertext, c3...) return append([]byte{0x04}, ciphertext...), nil default: return append([]byte{0x04}, c...), nil } } } // Decrypt decrypts data using ECC. func Decrypt(priv *PrivateKey, data []byte, mode int) ([]byte, error) { switch mode { case 0: data = data[1:] case 1: data = data[1:] c1 := make([]byte, 64) c2 := make([]byte, len(data)-96) c3 := make([]byte, 32) copy(c1, data[:64]) //x1,y1 copy(c2, data[64:len(data)-32]) copy(c3, data[len(data)-32:]) c := []byte{} c = append(c, c1...) c = append(c, c3...) c = append(c, c2...) data = c default: data = data[1:] } length := len(data) - 96 curve := priv.Curve x := new(big.Int).SetBytes(data[:32]) y := new(big.Int).SetBytes(data[32:64]) x2, y2 := curve.ScalarMult(x, y, priv.D.Bytes()) x2Buf := x2.Bytes() y2Buf := y2.Bytes() if n := len(x2Buf); n < 32 { x2Buf = append(zeroByteSlice()[:32-n], x2Buf...) } if n := len(y2Buf); n < 32 { y2Buf = append(zeroByteSlice()[:32-n], y2Buf...) } c, ok := ikdf(length, x2Buf, y2Buf) if !ok { return nil, errors.New("Decrypt: failed to decrypt") } for i := 0; i < length; i++ { c[i] ^= data[i+96] } tm := []byte{} tm = append(tm, x2Buf...) tm = append(tm, c...) tm = append(tm, y2Buf...) Sum256 := func(msg []byte) []byte { res := sha256.New() res.Write(msg) hash := res.Sum(nil) return []byte(hash) } h := Sum256(tm) if bytes.Compare(h, data[64:96]) != 0 { return c, errors.New("Decrypt: failed to decrypt") } return c, nil } // intToBytes converts an integer to a byte slice. func intToBytes(x int) []byte { var buf = make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(x)) return buf } // ikdf is a Key Derivation Function (KDF). func ikdf(length int, x ...[]byte) ([]byte, bool) { var c []byte ct := 1 h := sha256.New() for i, j := 0, (length+31)/32; i < j; i++ { h.Reset() for _, xx := range x { h.Write(xx) } h.Write(intToBytes(ct)) hash := h.Sum(nil) if i+1 == j && length%32 != 0 { c = append(c, hash[:length%32]...) } else { c = append(c, hash...) } ct++ } for i := 0; i < length; i++ { if c[i] != 0 { return c, true } } return c, false } // zeroByteSlice returns a slice of zero bytes. func zeroByteSlice() []byte { return []byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "log" ) func main() { // Generate ECDSA keys privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { log.Fatal("Failed to generate private key:", err) } // Encrypt and decrypt using the generated keys ciphertxt, err := ecdsa.EncryptAsn1(privateKey.Public().(*ecdsa.PublicKey), []byte("message"), rand.Reader) if err != nil { log.Fatal("Failed to encrypt the message:", err) } fmt.Print("CipherText: ") fmt.Printf("%x\n", ciphertxt) plaintxt, err := ecdsa.DecryptAsn1(privateKey, ciphertxt) if err != nil { log.Fatal("Failed to decrypt the ciphertext:", err) } fmt.Print("PlainText: ") fmt.Printf("%s\n\n", plaintxt) }
This function is compatible with ECDSA (secp256r1), SM2 (sm2p256v1), Koblitz (secp256k1), ANSSI (frp256v1) and NUMS (numsp256t1) curves. With the advantage that NUMS Twisted Edwards is resistant to side-channel attacks.
./edgetk -pkey keygen -algorithm ecdsa [-prv Private.pem] [-pass "pass"] [-pub Public.pem]
./edgetk -pkey encrypt -algorithm ecdsa -key Public.pem inputfile.ext > cipher.txt
./edgetk -pkey decrypt -algorithm ecdsa -key Private.pem cipher.txt > plaintext.ext
./edgetk -pkey text -key Private.pem
./edgetk -pkey text -key Public.pem
Copyright (c) 2021 Pedro F. Albanese <pedroalbanese@hotmail.com>
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.