Go Lang RSA Cryptography

by Anish

Posted on Tuesday November 13 , 2018

This sample chapter extracted from the book, Go Lang Cryptography for Developers . The Book theme isCryptography is for EveryOne. Learn from Crypto Principle to Applied Cryptography With Practical Example


RSA

RSA (Rivest Shamir Adleman) is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and it is different from the decryption key which is kept secret (private).

  1. The original specification for encryption and signatures with RSA is PKCS#1.
  2. In version two called by OAEP and PSS.
  3. RSAES-OAEP and RSAES-PKCS1-v1_5 are two Encryption Schemes.
  4. RSAES-OAEP is recommended for new applications
  5. RSAES-PKCS1-v1_5 is included only for compatibility with existing applications, and is not recommended for new applications
  6. RSASSA-PSS and RSASSA-PKCS1-v1_5 are two Signature Schemes
  7. RSASSA-PSS is recommended for eventual adoption in new applications

Key Type

RSA public key and RSA private key are two key types. Together, an RSA public key and an RSA private key form an RSA key pair.

An RSA Public Key consists of two numbers:

  • the modulus (e.g. a 2,048 bit number)
  • the exponent (usually 65,537)

An RSA Private Key may have either of two representations.

  1. The First representation consists of
  • n the RSA modulus, a positive integer
  • d the RSA private exponent, a positive integer
  1. The Second representation consists of a quintuple (p, q, dP, dQ, qInv) and a (possibly empty) sequence of triplets (r_i, d_i, t_i)
    • p the first factor, a positive integer
    • q the second factor, a positive integer
    • dP the first factor’s CRT exponent, a positive integer
    • dQ the second factor’s CRT exponent, a positive integer
    • qInv the (first) CRT coefficient, a positive integer
    • r_i the i-th factor, a positive integer
    • d_i the i-th factor’s CRT exponent, a positive integer
    • t_i the i-th factor’s CRT coefficient, a positive integer

Key Size
RSA keys are typically 1024 to 4096 bits long

Go Package rsa implements RSA encryption as specified in PKCS#1.


Generating RSA keys

GenerateKey generates an RSA keypair of the given bit size using the random source random.

Go Lang function GenerateKey .

func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)

for example the following go snippet will generate RSA key pair of size 2048 and 1024.

import (  
   "crypto/rand"  
   "crypto/rsa" 
   "fmt" 
   "os"
   )  
  
func main() {  
   // Generate Alice RSA keys Of 2048 Buts  
  alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)  
  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey := alicePrivateKey.PublicKey  
  
   fmt.Println("Alice Private Key (2048) :  ", *alicePrivateKey)  
   fmt.Println("Alice Public key (2048) ", alicePublicKey)  
  
   alicePrivateKey, err = rsa.GenerateKey(rand.Reader, 1024)  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey = alicePrivateKey.PublicKey  
  
   fmt.Println("Alice Private Key (1024) :  ", *alicePrivateKey)  
   fmt.Println("Alice Public key (1024) ", alicePublicKey)  
}

The Output : This is sample output, it will varry when the program run again.

$ go rsakeygen.go
Alice Private Key (2048) :   {{252577609084739754533.....9407816575401] {10730....06322368 []}}
Alice Public key (2048)  {25257760908473975453370376......2333967994449957795953256930991 65537}
Alice Private Key (1024) :   {{12173262182204760381951.....449809420207757589211338372808685143776483631382443548335028637376375135294794510858217880714345611 []}}
Alice Public key (1024)  {121732621822047603819....3695643889226733382631 65537}


GenerateMultiPrimeKey

Having more than two prime factors is supported by the PKCS#1 standard. This is called a “multi-prime” key. Go lang function GenerateMultiPrimeKey generates a multi-prime RSA keypair of the given bit size and the given random source. The Syntax for this function is

func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey, error)

For example the below code will have Three Prime Key Generation

package main  
  
import (  
   "crypto/rand"  
   "crypto/rsa" 
   "fmt"
   )  
  
func main() {  
   size := 768  
  // Three Prime Key Generation  
  alicePrivateKey, err := rsa.GenerateMultiPrimeKey(rand.Reader, 3, size)  
   if err != nil {  
      fmt.Println(err.Error)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey := alicePrivateKey.PublicKey  
  
   fmt.Println("Alice Private Key   ", *alicePrivateKey)  
   fmt.Println("Alice Public key  ", alicePublicKey)  
}

The Output: This is sample output, it will varry when the program run again.

$ go rsamultiprime.go
Alice Private Key    {{126134908529216858.......0175654062308......285310633005259496110624705393824297217491809883570809824786867373239057274829274133328288879778577235477274574684098643}]}}
Alice Public key   {1261349085292168580024695...... 8464445720671240782572554239501756540623080792125882018501372944881076823102151631 65537}

RSA OAEP Encryption/Decryption

RSA is able to encrypt only a very limited amount of data, In order to encrypt reasonable amounts of data a hybrid scheme is commonly used: RSA is used to encrypt a key for a symmetric primitive like AES-GCM.

  • EncryptOAEP encrypts the given message with RSA-OAEP, encryption is performed using RSA public key.
  • DecryptOAEP decrypts the ciphertext message with RSA-OAEP, decryption is performed using RSA private key.

The Go Function for OAEP encryption and decryption.

 func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error)
 func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error)

The following code will show, how the RSA key pair is generated and then performed encryption and decryption using OAEP encryption scheme.

package main  
  
import (  
   "crypto/rand"  
   "crypto/rsa"
   "crypto/sha256" 
   "encoding/base64" 
   "fmt" 
   "os"
   )  
func main() {  
   // Generate Alice RSA keys Of 2048 Buts  
  alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
  alicePublicKey := alicePrivateKey.PublicKey  
  secretMessage := "Hello 8gwifi.org"  
  encryptedMessage := EncryptOAEP(secretMessage,alicePublicKey);  
  fmt.Println("Cipher Text  ", encryptedMessage)  
  DecryptOAEP(encryptedMessage, *alicePrivateKey )  
} 

The RSA OAEP Encryption

  • The Encryption logic is performed using RSA public key.
  • The random parameter ensure encrypting the same message twice doesn’t result in the same ciphertext
  • Encryption and decryption of a given message must use the same hash function
  • The label parameter may contain arbitrary data that will not be encrypted.
func EncryptOAEP(secretMessage string, pubkey rsa.PublicKey)  (string) {  
   label := []byte("OAEP Encrypted")  
   // crypto/rand.Reader is a good source of entropy for randomizing the  
   // encryption function.  
   rng := rand.Reader   
   ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, &pubkey, []byte(secretMessage),    label)  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from encryption: %s\n", err)  
      return "Error from encryption";  
   }  
   return base64.StdEncoding.EncodeToString(ciphertext)  
}  

The RSA OAEP Decryption

func DecryptOAEP(cipherText string, privKey rsa.PrivateKey)  (string) {  
   ct,_ := base64.StdEncoding.DecodeString(cipherText)  
   label := []byte("OAEP Encrypted")  
  
   // crypto/rand.Reader is a good source of entropy for blinding the RSA  
 // operation.  
   rng := rand.Reader   
   plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, &privKey, ct, label)  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from decryption: %s\n", err)  
      return "Error from Decryption";  
   }  
   fmt.Printf("Plaintext: %s\n", string(plaintext))  
 
   return string(plaintext)  
}

The Output

$ go rsaencdec.go
Original Text   Hello 8gwifi.org
Cipher Text   6SXap813U4zGf6c9Ajyjl2sr1UE/ztv+lrZVQR2q/2cp6skSA+h0Y+4XL7r1y2YNi63d3c7CgYPKyqvZJiQq6OTEQ9VCbaCoHTglTYyV/0pbN7gmiYD6figXbgw1ScUg/SQyS2XbI278EwyFM2D2W15jppKR9zRHqEJyqhWdfCWiKlUFSdxGaXmjhWvZ4/EUqzYHmS3+VwQAmZmYfvKLEcjAGvfstp0sC9C7IcTi3zil5Guz4RovJvL540Hw75bDr9gQltddtir7L22yw9M9GM7YGxsyZNNez4c5EhA9vWJSJB+glrLfu60d9W2/NbCjF7mf6pOPfnkdVKhUzsjlcA==
Plaintext: Hello 8gwifi.org

RSA_PKCS1-V1_5 Sign/Verify

SignPKCS1v15 calculates the signature of hashed using RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5.

The Go function

func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error)

VerifyPKCS1v15 verifies an RSA PKCS#1 v1.5 signature. hashed is the result of hashing the input message.

The Go function

func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error
  • Signature generation is performed using RSA private Key
  • Signature Verification is performed with RSA public Key.

The following example code will generate RSA keys of size 2048 and then perform RSA Sign/verify using the PKCS1-V1_5 signature scheme.

package main  
  
import (  
   "crypto"  
   "crypto/rand"
   "crypto/rsa"
   "crypto/sha256" 
   "encoding/base64" 
   "fmt" 
   "os"
   )  
  
func main() {  
  
   // Generate Alice RSA keys Of 2048 Buts  
  alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey := alicePrivateKey.PublicKey  
   secretMessage := "Hello 8gwifi.org"  
   fmt.Println("Original Text  ", secretMessage)  
   signature := SignPKCS1v15(secretMessage,*alicePrivateKey);  
   fmt.Println("Singature :  ", signature)  
   verif := VerifyPKCS1v15(signature, secretMessage,  alicePublicKey )  
   fmt.Println(verif)  
}
  • Only small messages can be signed directly, thus the hash of a message, rather than the message itself, is signed.
func SignPKCS1v15(plaintext string, privKey rsa.PrivateKey)  (string) {  
   // crypto/rand.Reader is a good source of entropy for blinding the RSA  
 // operation.  
   rng := rand.Reader   
   hashed := sha256.Sum256([]byte(plaintext))  
   signature, err := rsa.SignPKCS1v15(rng, &privKey, crypto.SHA256, hashed[:])  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from signing: %s\n", err)  
      return "Error from signing"  
  }  
   return base64.StdEncoding.EncodeToString(signature)  
}
  • Signature Verification is performed using RSA Public key, and verification is done along with the original message.
func VerifyPKCS1v15(signature string, plaintext string, pubkey rsa.PublicKey) (string) {  
   sig, _ := base64.StdEncoding.DecodeString(signature)  
   hashed := sha256.Sum256([]byte(plaintext))  
   err := rsa.VerifyPKCS1v15(&pubkey, crypto.SHA256, hashed[:], sig)  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from verification: %s\n", err)  
      return "Error from verification:"  
  }  
   return "Signature Verification Passed"  
}

The output

$ go rsasignverify.go
Original Text   Hello 8gwifi.org
Signature :   FwEix1XfSsPB+fTeWCSqSHlkT+5gSmXDKG/rgjIlvvH15qTUJAbJfkpReIvw/xM+8v6RclwJg8TE8c2v20WKbvTC37B46XrrxwMz9w0gGOde4kjFTKqb6TiERqkrgAO/6fFGYWQtmqJf3j/18ztmYGcYRgVZcNExTyVA7eskVw3jLAxiMDdgFjCvBLJrtpiGxl/jUGf27dbWU6engRPKobcwAN0YBlrJbetVHS0VsINqPXXg7GfrSrLZ0YI7R6yR2yaRsn0nexPs8ybyHqpIKd0q1PNBbQ4fs+BWrcz1UZnbXOh9jrvF/yLcR9cPeUBD1YV3GNtpjyqILuiM3LjiQQ==
Signature Verification Passed

RSA_PSS Sign/Verify

Go Lang function SignPSS calculates the signature of hashed using RSASSA-PSS, The signature generation is performed using RSA private key.

func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte, opts *PSSOptions) ([]byte, error)

Go Lang function VerifyPSS Verify the signature of hashed using RSASSA-PSS, The signature verification is performed using RSA public key along with plain text message.

func VerifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, opts *PSSOptions) error

Here is the example of performing PSS Signature generation and verification.

  • An RSA key is generated.
  • Performed the Signature generation of the given plain text message using the SignPSS method.
  • After that the signature is passed VerifyPSS method to verify the signature.
package main  
  
import (  
   "crypto"  
   "crypto/rand" 
   "crypto/rsa" 
   "crypto/sha256" 
   "encoding/base64" 
   "fmt" 
   "os")  
  
func main() {  
  
   // Generate Alice RSA keys Of 2048 Buts  
   alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey := alicePrivateKey.PublicKey  
   secretMessage := "Hello 8gwifi.org"  
   fmt.Println("Original Text  ", secretMessage)  
   signature := SignPSS(secretMessage,*alicePrivateKey);  
   fmt.Println("Signature :  ", signature)  
   verif := VerifyPSS(signature, secretMessage,  alicePublicKey )  
   fmt.Println(verif)  
}

Logic to perform signing of the message.

func SignPSS(plaintext string, privKey rsa.PrivateKey)  (string) {  
   // crypto/rand.Reader is a good source of entropy for blinding the RSA operation.  
   rng := rand.Reader  
   hashed := sha256.Sum256([]byte(plaintext))  
   var opts rsa.PSSOptions  
   signature, err := rsa.SignPSS(rng, &privKey, crypto.SHA256, hashed[:],&opts)  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from signing: %s\n", err)  
      return "Error from signing"  
  }  
   return base64.StdEncoding.EncodeToString(signature)  
}

Logic to perform verification of the signature using the RSA public key,

func VerifyPSS(signature string, plaintext string, pubkey rsa.PublicKey) (string) {  
   sig, _ := base64.StdEncoding.DecodeString(signature)  
   hashed := sha256.Sum256([]byte(plaintext))  
   var opts rsa.PSSOptions  
   err := rsa.VerifyPSS(&pubkey, crypto.SHA256, hashed[:], sig,&opts)  
   if err != nil {  
      fmt.Fprintf(os.Stderr, "Error from verification: %s\n", err)  
      return "Error from verification:"  
  }  
   return "Signature Verification Passed"  
}

The output

$ go rsapsssignverify.go
Original Text   Hello 8gwifi.org
Signature :   fMKqR0UIinPM4YgqjWqtQioi9SWLMASMRgoyAYUUlCy3yUHpNnXDjhVjRHAAUMDM1LoVpuCeYsdDttS3EJdAeyuF7CxRg9mv24myUa+wNTA8lVOP+OOg8hUKHSa7pMHvpuZSuW35fYcQrbaUltcrlsqN9ndcNUY2GEosuHwZwm/jlt+pUl/ONrETCdwXeq2z3CLnrBbuuRdw9F2+YrlB1jyWDjkR2kIeYjw8MS7UtZkqZ4f3DZNbtCAYdw+DJR/I2Ej0mhRRiFYPuUL2w0KUWxSaPYjZaYQJWmJfVDcVAluwEns0H7RFrqGdh2Zxa70uEcS1AT3Omo77ItAn0L7omw==
Signature Verification Passed

Export RSA Key to PEM Format

  • First Generate an RSA Key pair.
package main  
  
import (  
   "crypto/rand"  
   "crypto/rsa" 
   "crypto/x509" 
   "encoding/asn1" 
   "encoding/pem"
   "fmt"
   "os" 
   "bufio"
   )  
  
func main() {  
   // Generate Alice RSA keys Of 2048 Buts  
    alicePrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)  
   if err != nil {  
      fmt.Println(err.Error)  
      os.Exit(1)  
   }  
   // Extract Public Key from RSA Private Key  
   alicePublicKey := alicePrivateKey.PublicKey  
   fmt.Println("Alice Private Key :  ", *alicePrivateKey)  
   fmt.Println("Alice Public key ", alicePublicKey)
  • Once the RSA Key pair is generated store it in PEM format with pkcs encoding.

    // Method to store the RSA keys in pkcs8 Format  
    savePKCS8RSAPEMKey("/tmp/alicepriv.pem", alicePrivateKey)  
    // Method to store the RSA keys in pkcs1 Format  
    savePKCS1RSAPublicPEMKey("/tmp/alicepub.pem",&alicePublicKey)  
    // Method to Strore in Public Key format 
    savePublicPEMKey("/tmp/alicepub1.pem",alicePublicKey)
    
  • Method to store the RSA keys in pkcs8 Format -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY-----

    func savePKCS8RSAPEMKey(fName string, key *rsa.PrivateKey) {  
       outFile, err := os.Create(fName)  
       if err != nil {  
          fmt.Println("Fatal error ", err.Error())  
          os.Exit(1)  
       }  
       defer outFile.Close()  
       //converts a private key to ASN.1 DER encoded form.  
      var privateKey = &pem.Block{  
          Type:  "RSA PRIVATE KEY",  
          Bytes: x509.MarshalPKCS1PrivateKey(key),  
       }  
       err = pem.Encode(outFile, privateKey)  
       if err != nil {  
          fmt.Println("Fatal error ", err.Error())  
          os.Exit(1)  
       }  
    }
    
  • Method to store the RSA keys in pkcs1 Format -----BEGIN RSA PUBLIC KEY----- and -----END RSA PUBLIC KEY-----

func savePKCS1RSAPublicPEMKey(fName string, pubkey *rsa.PublicKey) {  
   //converts an RSA public key to PKCS#1, ASN.1 DER form.  
  var pemkey = &pem.Block{  
      Type:  "RSA PUBLIC KEY",  
      Bytes: x509.MarshalPKCS1PublicKey(pubkey),  
   }  
   pemfile, err := os.Create(fName)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   defer pemfile.Close()  
   err = pem.Encode(pemfile, pemkey)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
}
  • Method to store the RSA keys in public key Format -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----
func savePublicPEMKey(fileName string, pubkey rsa.PublicKey) {  
   asn1Bytes, err := asn1.Marshal(pubkey)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   var pemkey = &pem.Block{  
      Type:  "PUBLIC KEY",  
      Bytes: asn1Bytes,  
   }  
   
   pemfile, err := os.Create(fileName)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   defer pemfile.Close()  
   err = pem.Encode(pemfile, pemkey)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
}

Import PEM Key to RSA Format

  • To import PEM key first read the file, parse it and then use pem.Decode to construct the required interface.

    importedAliceRSAPrivateKey := *loadRSAPrivatePemKey("/tmp/alicepriv.pem");  
    importedAliceRSAPublicKey := loadPublicPemKey("/tmp/alicepub.pem");  
    fmt.Println("Imported Alice Private Key", importedAliceRSAPrivateKey)  
    fmt.Println("Imported Alice Public Key", *importedAliceRSAPublicKey)
    }
    
  • Load RSA private key which is stored in PEM format and create RSA private Key using go x509.ParsePKCS1PrivateKey

func loadRSAPrivatePemKey(fileName string)  (*rsa.PrivateKey)  {  
   privateKeyFile, err := os.Open(fileName)  
  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
  
   pemfileinfo, _ := privateKeyFile.Stat()  
   var size int64 = pemfileinfo.Size()  
   pembytes := make([]byte, size)  
   buffer := bufio.NewReader(privateKeyFile)  
   _, err = buffer.Read(pembytes)  
  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   data, _ := pem.Decode([]byte(pembytes))  
   privateKeyFile.Close()  
   privateKeyImported, err := x509.ParsePKCS1PrivateKey(data.Bytes)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   return privateKeyImported  
}
  • Load public key stored in PEM format and create RSA public Key using go x509.ParsePKCS1PublicKey
func loadPublicPemKey (fileName string)  (*rsa.PublicKey) {  
  
   publicKeyFile,err := os.Open(fileName)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
  
   pemfileinfo, _ := publicKeyFile.Stat()  
  
   size  := pemfileinfo.Size()  
   pembytes := make([]byte, size)  
   buffer := bufio.NewReader(publicKeyFile)  
   _, err = buffer.Read(pembytes)  
   data, _ := pem.Decode([]byte(pembytes))  
   publicKeyFile.Close()  
   publicKeyFileImported, err := x509.ParsePKCS1PublicKey(data.Bytes)  
   if err != nil {  
      fmt.Println("Fatal error ", err.Error())  
      os.Exit(1)  
   }  
   return publicKeyFileImported  
}

The output : Alice private and public key before import and after import are same.

$ go rsapem.go
Alice Private Key :   {27955432259048807217......5903087222243000281118047 65537}
Alice Public key  {2795543225904880721713151983.....3087222243000281118047 65537}
Imported Alice Private Key {{27955432259048807217......5903087222243000281118047 65537} 
Imported Alice Public Key {2795543225904880721713151983.....3087222243000281118047 65537}

Thanku for reading !!! Give a Share for Support


Your Support Matters!

Instead of directly asking for donations, I'm thrilled to offer you all nine of my books for just $9 on leanpub By grabbing this bundle you not only help cover my coffee, beer, and Amazon bills but also play a crucial role in advancing and refining this project. Your contribution is indispensable, and I'm genuinely grateful for your involvement in this journey!

Any private key value that you enter or we generate is not stored on this site, this tool is provided via an HTTPS URL to ensure that private keys cannot be stolen, for extra security run this software on your network, no cloud dependency




python Cryptography Topics
Topics
For Coffee/ Beer/ Amazon Bill and further development of the project Support by Purchasing, The Modern Cryptography CookBook for Just $9 Coupon Price

Kubernetes for DevOps

Hello Dockerfile

Cryptography for Python Developers

Cryptography for JavaScript Developers

Go lang ryptography for Developers

Here