Encryption in the client

Client-side normalization, encryption, and hashing

In addition to the option of sending the data to be monitored in plain text to omniac Business, we also offer the option of receiving the data already normalized, encrypted, and hashed. The latter is also the method we recommend, as it ensures that your users’ personal attributes never leave your company context in plain text. The processing steps required for this must be identical to those we perform before using the data to search our data leak database. This procedure is briefly explained below.

Retrieve tenant

In your tenant, you will find all the necessary information for normalization, encryption, and hashing. In the “available_attributes” field, you will find the normalization rules, in the “hash_algorithm” field the hash function to be used, and in the “encryption_key” field the public key to encrypt the data. Each of these steps is now briefly explained.

Example: Email

The normalization step must be performed at the beginning and before any further processing. Let’s look at the example of email (found in the tenant’s “available_attributes”):

{
    "key": "email",
    "encrypted": true,
    "split": false,
    "normalize": [
        "DeleteQuotationMarks",
        "DeleteTrailingComma",
        "UnicodeNormalization",
        "Strip",
        "Lowercase"
    ],
    "mask": {
        "task": "email",
        "begin": 0,
        "end": 0
    }
},

All of the following steps are performed on the normalised version of the email. Next comes hashing. The algorithm is specified in the tenant, but currently it is always SHA-256. Encryption is not required for every attribute (see boolean field encrypted). If encrypted=true the PKCS1 algorithm is used. Here is an example implementation in Go:

func EncryptWithPublicKey(pubKey string, data string) (string, error) {
	publicKeyBytes, err := base64.StdEncoding.DecodeString(pubKey)
	if err != nil {
		return "", errors.New("failed to decode public key: " + err.Error())
	}

	rsaPublicKey, err := ParseRsaPublicKeyFromPemStr(string(publicKeyBytes))
	if err != nil {
		return "", errors.New("ParseRsaPublicKey: " + err.Error())
	}
	rng := rand.Reader
    ciphertext, err := rsa.EncryptPKCS1v15(rng, rsaPublicKey, []byte(data))
	if err != nil {
		return "", errors.New("failed to encrypt data: " + err.Error())
	}

	// Encode the encrypted data to base64 for easy transmission or storage
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

Example: Name (split attribute)

If the “split” parameter is set in the AttributConfig, omniac Business expects that after normalisation, the values will be separated at each space and the resulting parts will be normalised and hashed again individually. However, encryption still happens on the entire value. Finally, the hashed values are transmitted to omniac Business as a string separated by commas. Here is an example:

{
    "key": "name",
    "encrypted": true,
    "split": true,
    "normalize": [
        "DeleteQuotationMarks",
        "DeleteTrailingComma",
        "ReplaceSymbols",
        "DeleteConsecutiveSpaces",
        "UnicodeNormalization",
        "Strip",
        "Lowercase"
    ],
    "mask": {
        "task": "",
        "begin": 0,
        "end": 0
    }
},

If, for example, the name is to be monitored, the following steps are necessary because split=true. Name example: “Max Mustermann”

  1. Normalisation of the entire string (“max mustermann”)
  2. Encryption of the normalised string (“Q2CWI6xZ6r2QsS/tJCARqzFWEXh+aeRuClgaX4AzMrNAXrgyQcfq6/lYsCGvSPVa1jTWF7zd4fmi2AMh3DbirRNpfCJNNZcW2mE1Iw0EhilpYen6nYAMPtxb5zHhsb2DfaKr0qOFal1zIjDM+KiAbLaiOF+wTajGmH/3Bt2rwvK30/qDta3u3oplKzLVNqaoWpqGqiv0cKx9ytday8YqxqbqNcGZf1ztZVUAxkS4M0UHsVHVnHDK6ADcZoz2scdJU9nwipb0C2dXGePlE/hBBttkzq6lR2vExfIbjTQ+QEhzQiUdvz9WeuckHLRG8jGhMnSpIUZ9Pt/w2H1qAoEN0A==”)
  3. Splitting the string at the spaces ([“max”, “mustermann”])
  4. Normalise the parts ([“max”, “mustermann”])
  5. Hash the parts ([“9baf3a40312f39849f46dad1040f2f039f1cffa1238c41e9db675315cfad39b6”, “e32a370b7912ad78cc6a88fda605a5b3657e9c3b164cee669364aaf3f8cdbb36”])
  6. Merge the hashed parts as a string separated by commas (“9baf3a40312f39849f46dad1040f2f039f1cffa1238c41e9db675315cfad39b6, e32a370b7912ad78cc6a88fda605a5b3657e9c3b164cee669364aaf3f8cdbb36”)

Sending the attributes

While a normal PUT /v1/profiles/:profile_id/attributes call body looks like this:

[
     {
        "type": {
            "key": "name"
        },
        "value": "Max Mustermann",
    },
    {
        "type": {
            "key": "email"
        },
        "value": "test@example.com",
    },
]

we expect the following format for values hashed and encrypted in the client:

[
     {
        "type": {
            "key": "name"
        },
        "value": "M** ******mann",
        "hashed": "9baf3a40312f39849f46dad1040f2f039f1cffa1238c41e9db675315cfad39b6,e32a370b7912ad78cc6a88fda605a5b3657e9c3b164cee669364aaf3f8cdbb36",
        "encrypted": "Q2CWI6xZ6r2QsS/tJCARqzFWEXh+aeRuClgaX4AzMrNAXrgyQcfq6/lYsCGvSPVa1jTWF7zd4fmi2AMh3DbirRNpfCJNNZcW2mE1Iw0EhilpYen6nYAMPtxb5zHhsb2DfaKr0qOFal1zIjDM+KiAbLaiOF+wTajGmH/3Bt2rwvK30/qDta3u3oplKzLVNqaoWpqGqiv0cKx9ytday8YqxqbqNcGZf1ztZVUAxkS4M0UHsVHVnHDK6ADcZoz2scdJU9nwipb0C2dXGePlE/hBBttkzq6lR2vExfIbjTQ+QEhzQiUdvz9WeuckHLRG8jGhMnSpIUZ9Pt/w2H1qAoEN0A=="
    },
    {
        "type": {
            "key": "email"
        },
        "value": "te*****@**ple.com",
        "hashed": "973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b",
        "encrypted": "K/QKpKzdLLGRSayMrqPFqpBCN/vH6fDKWltqNqX1E0ZBLZQEya6rLqg8VTiomyC3hDnp3d+YFHYwXyFpCRjIXQ0uXA8Yz2fZWHnYdLVv5ua2gYAC9huCJmtM89wYBLINPq47gERwHUeiSzVxNk3D6XvwgvGQXd+N+y/A4XC+mhhe603CrC6lzY0N2e7QyQK5YBni9mfr0S+lMVN6CpGqBlnucKGaVXdPn9fBmwWvW3pkA4uoEhRQruD9fdIBFrOy388ctRtrmHFVlP5IWkwXxXas2CpLCarapgULJcO9pG6kG0RqE+NAiKgsZ2Jw3PA0ZLqjp0sIXH3xTzLPNZJ7xQ=="
    },
]

Now the attributes are stored and monitored by us, as in the version in which omniac Business takes over these steps.