Encrypt environment variables using X25519 and AES-GCM. - Generate an ephemeral keypair and compute a shared secret with the remote public key - Encrypt the JSON payload with AES-GCM using the shared secret - Return concatenation of ephemeral public key + IV + ciphertext as hex
(envs: List[EnvVar], public_key_hex: str)
| 39 | |
| 40 | |
| 41 | async def encrypt_env_vars(envs: List[EnvVar], public_key_hex: str) -> str: |
| 42 | """Encrypt environment variables using X25519 and AES-GCM. |
| 43 | |
| 44 | - Generate an ephemeral keypair and compute a shared secret with the remote |
| 45 | public key |
| 46 | - Encrypt the JSON payload with AES-GCM using the shared secret |
| 47 | - Return concatenation of ephemeral public key + IV + ciphertext as hex |
| 48 | """ |
| 49 | # Prepare environment data as JSON |
| 50 | env_dict = {"env": [{"key": env.key, "value": env.value} for env in envs]} |
| 51 | env_json = json.dumps(env_dict).encode("utf-8") |
| 52 | |
| 53 | # Generate private key and derive public key |
| 54 | private_key = X25519PrivateKey.generate() |
| 55 | public_key = private_key.public_key() |
| 56 | |
| 57 | # Get public key bytes (32 bytes for X25519) |
| 58 | public_key_bytes = public_key.public_bytes( |
| 59 | encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw |
| 60 | ) |
| 61 | |
| 62 | # Generate shared secret |
| 63 | remote_public_key = X25519PublicKey.from_public_bytes(hex_to_bytes(public_key_hex)) |
| 64 | shared_secret = private_key.exchange(remote_public_key) |
| 65 | |
| 66 | # Encrypt the data using AES-GCM |
| 67 | aesgcm = AESGCM(shared_secret) |
| 68 | iv = secrets.token_bytes(12) # 12 bytes IV for GCM |
| 69 | encrypted_data = aesgcm.encrypt(iv, env_json, None) |
| 70 | |
| 71 | # Combine all components: public_key + iv + encrypted_data |
| 72 | result = public_key_bytes + iv + encrypted_data |
| 73 | |
| 74 | return bytes_to_hex(result) |
| 75 | |
| 76 | |
| 77 | def encrypt_env_vars_sync(envs: List[EnvVar], public_key_hex: str) -> str: |