1# Set up asymmetric keys
2
3> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.
4
5Register an RSA-3072 asymmetric key pair from your cloud provider for use with OpenAI.
6
7Your private key stays in AWS KMS, Azure Key Vault, or Google Cloud KMS. OpenAI stores a copy of the corresponding public key at registration.
8
9Registering a key doesn't configure product features or storage. This guide
10 covers keys for [PSP
11 decryption](https://developers.openai.com/api/docs/guides/private-safety-processing#psp-decryption).
12
13## Before you start
14
15- Have your organization ID. Use the same organization for cloud trust, registration, and API access.
16- Create an [organization Admin API key](https://developers.openai.com/api/docs/guides/admin-apis) with external-key read/write access. Load it securely as `OPENAI_ADMIN_KEY`; project inference keys won't work.
17- Have permission to create an enabled RSA-3072 key and grant OpenAI the scoped public-key read access described in this guide.
18- Restrict private-key use to authorized users and retain old key versions while records depend on them.
19
20## Set up a cloud key
21
22Follow the steps for your provider before registering the key. Each setup uses RSA-3072 and grants OpenAI only public-key and metadata reads.
23
24
25
26<a id="aws-kms"></a>
27
28
29
30### AWS KMS
31
32
33
34#### 1. Create an RSA key
35
36Open **KMS** > **Customer managed keys** > **Create key** and choose your region. Use these settings:
37
38- **Key type:** Asymmetric
39- **Key usage:** Encrypt and decrypt
40- **Key spec:** `RSA_3072`
41
42Finish creating the key with your own key administrators. Keep it enabled and copy its full ARN, ending in `key/<CUSTOMER_KMS_KEY_ID>`. Don't use an alias ARN.
43
44#### 2. Create a public-key read policy
45
46Open **IAM** > **Policies** > **Create policy** > **JSON**. Replace `<CUSTOMER_KMS_KEY_ARN>` and save this policy as `OpenAI-PublicKeyRead`.
47
48```json
49{
50 "Version": "2012-10-17",
51 "Statement": [
52 {
53 "Effect": "Allow",
54 "Action": ["kms:DescribeKey", "kms:GetPublicKey"],
55 "Resource": "<CUSTOMER_KMS_KEY_ARN>"
56 }
57 ]
58}
59```
60
61The key supports decryption, but OpenAI only needs these read permissions. Don't grant OpenAI key-user or key-administrator access.
62
63#### 3. Create the trusted IAM role
64
65Open **IAM** > **Roles** > **Create role** > **Custom trust policy**. Replace `<OPENAI_ORG_ID>` with your OpenAI organization ID. Keep the OpenAI principal unchanged.
66
67```json
68{
69 "Version": "2012-10-17",
70 "Statement": [
71 {
72 "Effect": "Allow",
73 "Principal": {
74 "AWS": "arn:aws:iam::790389265272:role/EnterpriseKeyManagement"
75 },
76 "Action": "sts:AssumeRole",
77 "Condition": {
78 "StringEquals": {
79 "sts:ExternalId": "<OPENAI_ORG_ID>"
80 }
81 }
82 }
83 ]
84}
85```
86
87Attach `OpenAI-PublicKeyRead` and create the role. Copy its ARN. Use the same organization ID as `external_id` when registering the key.
88
89#### 4. Check the KMS key policy
90
91Confirm that the key policy authorizes the role. If it doesn't delegate authorization to IAM, add this statement. Replace `<CUSTOMER_ROLE_ARN>` and preserve existing administrator access.
92
93In this KMS key policy, `"Resource": "*"` applies only to the key the policy is attached to. The IAM policy uses the exact key ARN.
94
95```json
96{
97 "Sid": "AllowPublicKeyRead",
98 "Effect": "Allow",
99 "Principal": {
100 "AWS": "<CUSTOMER_ROLE_ARN>"
101 },
102 "Action": ["kms:DescribeKey", "kms:GetPublicKey"],
103 "Resource": "*"
104}
105```
106
107Keep the role ARN and key ARN for registration.
108
109
110
111
112
113
114
115<a id="azure-key-vault"></a>
116
117
118
119### Azure Key Vault
120
121
122
123#### 1. Add the OpenAI enterprise application
124
125Ask a tenant administrator to add **EKM - OpenAI Azure** to your tenant. Its application ID is `20a14814-5ab7-4612-a671-1382b412bf93`.
126
127Install Azure CLI, then sign in and check for the application:
128
129```bash
130az login --tenant '<CUSTOMER_TENANT_ID>'
131az ad sp show --id '20a14814-5ab7-4612-a671-1382b412bf93' \
132 --query '{name:displayName,objectId:id}' -o table
133```
134
135If the application is missing, add it:
136
137```bash
138az ad sp create --id '20a14814-5ab7-4612-a671-1382b412bf93' \
139 --query '{name:displayName,objectId:id}' -o table
140```
141
142Save the tenant-local object ID for the role assignment.
143
144#### 2. Create the key
145
146Open **Key vaults** > **Create**. Select your subscription, resource group, vault name, and region. Under **Access configuration**, choose **Azure role-based access control**.
147
148Your administrator needs a role that permits key creation, such as **Key Vault Crypto Officer** on this vault. Don't grant that role to OpenAI.
149
150In the vault, open **Objects** > **Keys** > **Generate/Import** and use:
151
152- **Name:** `<OPENAI_ORG_ID>--asymmetric-key`, using your OpenAI organization ID
153- **Key type:** RSA
154- **RSA key size:** 3072
155- **Enabled:** Yes
156
157If you set activation or expiration dates, make sure the key is currently valid. Include **Decrypt** in the key's permitted operations. OpenAI receives key-read access only.
158
159RSA-HSM keys in a Key Vault are also supported. Standalone Managed HSM endpoints aren't supported.
160
161#### 3. Grant key-read access
162
163Create a custom role with the key-read `DataAction`. Save this as `public-key-reader.json` and replace the subscription and resource-group values.
164
165```json
166{
167 "Name": "OpenAI Public Key Reader",
168 "IsCustom": true,
169 "Description": "Read key metadata and public keys.",
170 "Actions": [],
171 "NotActions": [],
172 "DataActions": ["Microsoft.KeyVault/vaults/keys/read"],
173 "NotDataActions": [],
174 "AssignableScopes": [
175 "/subscriptions/<CUSTOMER_SUBSCRIPTION_ID>/resourceGroups/<CUSTOMER_RESOURCE_GROUP>"
176 ]
177}
178```
179
180Set `VAULT_RESOURCE_ID` to the vault's Azure resource ID from **Properties**, not its HTTPS URI. Set `OPENAI_SP_ID` to the application's tenant-local object ID, then assign the role on the key:
181
182```bash
183az role definition create --role-definition @public-key-reader.json
184
185OPENAI_SP_ID='<CUSTOMER_OPENAI_SP_OBJECT_ID>'
186VAULT_RESOURCE_ID='<CUSTOMER_VAULT_RESOURCE_ID>'
187KEY_NAME='<OPENAI_ORG_ID>--asymmetric-key'
188KEY_SCOPE="$VAULT_RESOURCE_ID/keys/$KEY_NAME"
189
190az role assignment create \
191 --assignee-object-id "$OPENAI_SP_ID" \
192 --assignee-principal-type ServicePrincipal \
193 --role 'OpenAI Public Key Reader' \
194 --scope "$KEY_SCOPE"
195```
196
197For a vault using legacy access policies, grant key **Get** instead. Keep its existing permission model.
198
199#### 4. Record the exact version
200
201Record your tenant ID, vault URI, and key name. Open the enabled key version and copy the 32-character hexadecimal version from the end of its **Key Identifier**.
202
203
204
205
206
207
208
209<a id="google-cloud-kms"></a>
210
211
212
213### Google Cloud KMS
214
215
216
217#### 1. Prepare the project and key
218
219Enable Cloud KMS in the key project and IAM and Security Token Service in the federation project. If these projects differ, enable Cloud KMS in both.
220
221Record your federation project's 12-digit project number. The KMS project may be different.
222
223Open **Security** > **Key Management**. Create a key ring, then create a key with:
224
225- **Protection level:** Software
226- **Purpose:** Asymmetric decrypt
227- **Algorithm:** 3072 bit RSA - OAEP Padding - SHA256 Digest
228
229Copy the KMS project ID, location, key ring, key name, and enabled version. Use a specific version such as `"1"`, not `"latest"` or `"0"`. Asymmetric keys don't have a primary version.
230
231#### 2. Configure workload identity federation
232
233Open **IAM & Admin** > **Workload Identity Federation**. Create a global pool, such as `openai-keys`, and an OIDC provider, such as `openai-google`, with these settings:
234
235| Provider setting | Value |
236| ------------------- | ------------------------------------------ |
237| Issuer URL | `https://accounts.google.com` |
238| Allowed audience | Your exact OpenAI organization ID |
239| Attribute mapping | `google.subject = assertion.sub` |
240| Attribute condition | `assertion.sub == '105900137572174660365'` |
241
242Use your OpenAI organization ID as the audience. Keep the issuer and OpenAI subject unchanged.
243
244#### 3. Create and assign the key-reader role
245
246In **IAM & Admin** > **Roles**, create a custom role in the KMS project with:
247
248```text
249cloudkms.cryptoKeyVersions.get
250cloudkms.cryptoKeyVersions.viewPublicKey
251```
252
253Both permissions are required; public-key-viewer alone is insufficient. No decrypt, sign, or service-account impersonation grant is needed.
254
255On the key's **Permissions** tab, choose **Grant access**. Assign the role to this principal, replacing `<CUSTOMER_GCP_PROJECT_NUMBER>` and `<CUSTOMER_GCP_POOL_ID>`:
256
257```text
258principal://iam.googleapis.com/projects/<CUSTOMER_GCP_PROJECT_NUMBER>/locations/global/workloadIdentityPools/<CUSTOMER_GCP_POOL_ID>/subject/105900137572174660365
259```
260
261Use your pool's project number and scope the grant to this key. Key-level IAM covers its versions. Save the pool and provider IDs, organization audience, and KMS identifiers for registration.
262
263
264
265
266
267## Register your key
268
269The examples use `curl` and a Bash-compatible shell. Load `OPENAI_ADMIN_KEY` securely, then set your organization ID:
270
271```bash
272API_BASE="https://api.openai.com/v1"
273ORG_ID="<OPENAI_ORG_ID>"
274
275: "${OPENAI_ADMIN_KEY:?Load your organization admin key securely}"
276
277KEYS_URL="$API_BASE/organization/external_keys"
278```
279
280### Prepare the registration request
281
282The `purpose` enum accepts `ekm` (the default) or `psp_decryption`. Use `psp_decryption` for the asymmetric keys in this guide; `ekm` is for Enterprise Key Management (EKM).
283
284Save your provider's request as `registration.json`. Replace the example values; JSON doesn't expand shell variables.
285
286#### AWS KMS request
287
288```json
289{
290 "type": "aws",
291 "purpose": "psp_decryption",
292 "name": "asymmetric-key-aws",
293 "role_arn": "<CUSTOMER_ROLE_ARN>",
294 "external_id": "<OPENAI_ORG_ID>",
295 "kms_arn": "<CUSTOMER_KMS_KEY_ARN>"
296}
297```
298
299#### Azure Key Vault request
300
301```json
302{
303 "type": "azure",
304 "purpose": "psp_decryption",
305 "name": "asymmetric-key-azure",
306 "tenant_id": "<CUSTOMER_TENANT_ID>",
307 "vault_uri": "https://<CUSTOMER_VAULT_NAME>.vault.azure.net",
308 "key_name": "<OPENAI_ORG_ID>--asymmetric-key",
309 "key_version": "<CUSTOMER_KEY_VERSION>"
310}
311```
312
313#### Google Cloud KMS request
314
315```json
316{
317 "type": "gcp",
318 "purpose": "psp_decryption",
319 "name": "asymmetric-key-gcp",
320 "workload_identity_project_number": "<CUSTOMER_GCP_PROJECT_NUMBER>",
321 "workload_identity_pool_id": "openai-keys",
322 "workload_identity_provider_id": "openai-google",
323 "audience": "<OPENAI_ORG_ID>",
324 "kms_project_id": "<CUSTOMER_KMS_PROJECT_ID>",
325 "kms_key_ring_name": "asymmetric-keys",
326 "kms_key_name": "asymmetric-key",
327 "kms_key_location": "us-central1",
328 "kms_key_version": "1"
329}
330```
331
332### 1. Register and validate the key
333
334```bash
335curl --fail-with-body -sS "$KEYS_URL" \
336 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
337 -H "OpenAI-Organization: $ORG_ID" \
338 -H "Content-Type: application/json" \
339 --data @registration.json
340```
341
342Registration validates the key before saving it. Success returns HTTP 200 with an `extkey` ID and your provider configuration. Example response fields:
343
344```json
345{
346 "id": "extkey_11111111111111111111111111111111",
347 "object": "organization.external_key",
348 "name": "asymmetric-key-gcp",
349 "type": "gcp",
350 "purpose": "psp_decryption",
351 "api_project_ids": []
352}
353```
354
355Save the returned registration ID:
356
357```bash
358KEY_ID="<EXTERNAL_KEY_ID>"
359```
360
361### 2. Revalidate the key (optional)
362
363`GET` returns stored details. `POST /validate` rechecks current access and key validity against the saved public key. It doesn't decrypt records or refresh the snapshot.
364
365```bash
366curl --fail-with-body -sS "$KEYS_URL/$KEY_ID" \
367 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
368 -H "OpenAI-Organization: $ORG_ID"
369```
370
371```bash
372curl --fail-with-body -sS -X POST "$KEYS_URL/$KEY_ID/validate" \
373 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
374 -H "OpenAI-Organization: $ORG_ID"
375```
376
377Expected response (HTTP 200):
378
379```json
380{ "status": "success" }
381```
382
383Validate again after key, permission, or network changes. Allow IAM changes to propagate before retrying.
384
385To use your registered key for PSP decryption, [select it for a project](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption).
386
387## List keys
388
389Use `purpose=psp_decryption` to list these asymmetric-key registrations. Omitting the filter returns all purposes; `purpose=ekm` returns EKM only.
390
391```bash
392curl --fail-with-body -sS --get "$KEYS_URL" \
393 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
394 -H "OpenAI-Organization: $ORG_ID" \
395 --data-urlencode "purpose=psp_decryption" \
396 --data-urlencode "order=asc" \
397 --data-urlencode "limit=20"
398```
399
400While `has_more` is `true`, request the next page with `after` set to `last_id`. Keep `purpose`, `order`, and `limit` unchanged.
401
402## Replace a key
403
4041. Create a replacement AWS key or Azure/GCP version and grant read access.
4052. Register the replacement.
4063. [Select the new `extkey` ID on the project](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption) and verify the selection.
407
408Registrations are fixed snapshots; cloud rotation doesn't update them. Keep old private keys for records that depend on them. Changing the selection doesn't migrate existing records.
409
410## Delete a key
411
412[Clear](https://developers.openai.com/api/docs/guides/private-safety-processing#clear-the-key-selection) or [replace](https://developers.openai.com/api/docs/guides/private-safety-processing#select-a-key-for-psp-decryption) the selection on every active project before deleting the registration:
413
414```bash
415curl --fail-with-body -sS -X DELETE "$KEYS_URL/$KEY_ID" \
416 -H "Authorization: Bearer $OPENAI_ADMIN_KEY" \
417 -H "OpenAI-Organization: $ORG_ID"
418```
419
420Deletion returns HTTP 200 with `object: "organization.external_key.deleted"`. It doesn't delete your cloud key.
421
422## Troubleshooting
423
424| Error | What to check |
425| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
426| 401 or 403 | Use an organization Admin API key and confirm access to external-key registration. |
427| 400 `external_key_provider_error` | Check cloud trust, read permissions, network access, and the key's enabled state and algorithm. |
428| 400 request-validation errors | Check the purpose, concrete AWS key ARN, and exact Azure/GCP version. |
429| 404 | Check the registration ID and organization. Deleted registrations and registrations from another organization can't be used. |
430| `external_key_used_by_active_projects` | Clear or replace all active project selections. |
431| Key no longer matches | Register the replacement and select its new ID. |
432
433For support, share the error and `x-request-id`. Never share credentials.
434
435## Related guides
436
437- [Decrypt PSP records](https://developers.openai.com/api/docs/guides/psp-decryption)
438- [ZDR with Private Safety Processing](https://developers.openai.com/api/docs/guides/private-safety-processing)
439- [Admin APIs](https://developers.openai.com/api/docs/guides/admin-apis)