Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions docs/getting-started/gs-encryption-decryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ The following code example encrypts requests and decrypts responses using C#. Th

This file requires .NET 7.0. You can use an earlier version if required, but it must be .NET Core 3.0 or later. To change the version, replace the [top-level statements](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements) with a Main method and the [using declarations](https://learn.microsoft.com/en-us/cpp/cpp/using-declaration?view=msvc-170) with [using statements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using).

</TabItem>
<TabItem value='go' label='Go'>

The following code example encrypts requests and decrypts responses using Go. The required parameters are shown at the bottom of the file, or by running `go run uid2_request.go`.

</TabItem>
</Tabs>

Expand Down Expand Up @@ -577,4 +582,246 @@ else
```

</TabItem>

<TabItem value='go' label='Go'>


```go title="uid2_request.go"
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)

const (
nonceLengthBytes = 8
gcmIVLengthBytes = 12
)

func main() {
subArgs := os.Args[1:]

if len(subArgs) != 3 && len(subArgs) != 4 {
printUsage()
os.Exit(1)
}

url := subArgs[0]

response, err := func() (map[string]interface{}, error) {
if subArgs[1] == "--refresh-token" {
return refresh(url, subArgs[2], subArgs[3])
} else {
return generate(url, subArgs[1], subArgs[2])
}
}()

if err != nil {
log.Fatal(err)
}

prettyPrint(response)
}

func refresh(url string, refreshToken string, refreshResponseKey string) (map[string]interface{}, error) {
fmt.Printf("Request: Sending refresh_token to %s\n", url)

response, err := http.Post(url, "", strings.NewReader(refreshToken))
if err != nil {
return nil, err
}

return deserializeResponse(response, refreshResponseKey, true)
}

func generate(url string, apiKey string, secret string) (map[string]interface{}, error) {
payload, err := io.ReadAll(os.Stdin)
if err != nil {
return nil, err
}

key, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return nil, err
}

unencryptedEnvelope, err := makeUnencryptedEnvelope(payload)
if err != nil {
return nil, err
}

envelope, err := makeEncryptedEnvelope(unencryptedEnvelope, key)
if err != nil {
return nil, err
}

req, err := http.NewRequest("POST", url, strings.NewReader(base64.StdEncoding.EncodeToString(envelope)))
if err != nil {
return nil, err
}

req.Header.Add("Authorization", "Bearer "+apiKey)

response, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

return deserializeResponse(response, secret, false)
}

func aesgcm(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

return cipher.NewGCM(block)
}

func decryptResponse(ciphertext string, key string) ([]byte, error) {
ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return nil, err
}

keyBytes, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return nil, err
}

aesgcm, err := aesgcm(keyBytes)
if err != nil {
return nil, err
}

iv := ciphertextBytes[:gcmIVLengthBytes]
return aesgcm.Open(nil, iv, ciphertextBytes[gcmIVLengthBytes:], nil)
}

func deserialize(bytes []byte) (map[string]interface{}, error) {
var anyJson map[string]interface{}
err := json.Unmarshal(bytes, &anyJson)
return anyJson, err
}

func prettyPrint(obj map[string]interface{}) error {
bytes, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}

fmt.Println(string(bytes))
return nil
}

func checkStatusCode(response *http.Response, body []byte) error {
if response.StatusCode != http.StatusOK {
return fmt.Errorf("Response: Error HTTP status code %d\n%s", response.StatusCode, body)
}

return nil
}

func makeUnencryptedEnvelope(payload []byte) ([]byte, error) {
timestamp := make([]byte, 8)
binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UnixMilli()))

nonce := make([]byte, nonceLengthBytes)
_, err := rand.Read(nonce)
if err != nil {
return nil, err
}

var body bytes.Buffer
body.Write(timestamp)
body.Write(nonce)
body.Write(payload)
return body.Bytes(), nil
}

func encrypt(plaintext []byte, iv []byte, key []byte) ([]byte, error) {
aesgcm, err := aesgcm(key)
if err != nil {
return nil, err
}

return aesgcm.Seal(nil, iv, plaintext, nil), nil
}

func makeEncryptedEnvelope(payload []byte, key []byte) ([]byte, error) {
iv := make([]byte, gcmIVLengthBytes)
_, err := rand.Read(iv)
if err != nil {
return nil, err
}

ciphertext, err := encrypt(payload, iv, key)
if err != nil {
return nil, err
}

var envelope bytes.Buffer
envelope.WriteByte(1)
envelope.Write(iv)
envelope.Write(ciphertext)
return envelope.Bytes(), nil
}

func deserializeResponse(response *http.Response, key string, isRefresh bool) (map[string]interface{}, error) {
defer response.Body.Close()

body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}

err = checkStatusCode(response, body)
if err != nil {
return nil, err
}

plaintext, err := decryptResponse(string(body), key)
if err != nil {
return nil, err
}

offset := 16
if isRefresh {
offset = 0
}

return deserialize(plaintext[offset:])
}

func printUsage() {
fmt.Println(`Usage:
echo '<json>' | go run uid2_request.go <url> <api_key> <client_secret>

Example:
echo '{"email": "test@example.com", "optout_check": 1}' | go run uid2_request.go https://prod.uidapi.com/v2/token/generate UID2-C-L-999-fCXrMM.fsR3mDqAXELtWWMS+xG1s7RdgRTMqdOH2qaAo= wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=


Refresh Token Usage:
go run uid2_request.go <url> --refresh-token <refresh_token> <refresh_response_key>

Refresh Token Usage example:
go run uid2_request.go https://prod.uidapi.com/v2/token/refresh --refresh-token AAAAAxxJ...(truncated, total 388 chars) v2ixfQv8eaYNBpDsk5ktJ1yT4445eT47iKC66YJfb1s=`)
}
```
</TabItem>

</Tabs>