I have been battling with some code:
I have 2 variants I have been testing:
def ds4 do
try do
private_key_pem = File.read!("priv/flows/private_unencrypted.pem")
# Decode PEM key explicitly
private_key_entry = :public_key.pem_decode(private_key_pem) |> hd()
IO.inspect(private_key_entry, label: "Private Key Entry")
# Correctly decode the private key
case private_key_entry do
{:RSAPrivateKey, rsa_key} ->
IO.inspect(rsa_key, label: "RSA Private Key Structure")
# Decrypt AES key
encrypted_aes_key =
Base.decode64!(
"i/j13zUEy37M00eFJYchyIyk+HIlmLs8X6gmJLO9UfiljUmQCX1kYVPYfVRbS+5moWKtkIc0K/KG59CObmT8sMbhSXFEbnCKL7bbLka80NC5lqoill4LKrAtOaWJ/Zxbw7YjWS1+zinkIZZCbJ0OGZjH24XVgx3pVx5PYK53JDSYt6kUo1Kt15uc5M7zne1/T46c5YCw2BnSGDvXX+74W7T8xo+dTl6krOzFGuxNXFOYtG3coFK3Ad4eteMIhlsxpoRjFggfIWv9VuA7TT9AOQ9mdF2juN98Hu0hm3kSi5IHHGagTq+UwrspnMl76kw+GZXinjQNSI2ZV8yYYHxQ=="
)
decrypted_aes_key =
:crypto.private_decrypt(:rsa, encrypted_aes_key, rsa_key,
rsa_padding: :rsa_pkcs1_oaep_padding,
oaep_hash: :sha256
)
IO.inspect(decrypted_aes_key, label: "Decrypted AES Key")
decrypted_aes_key
other ->
IO.inspect(other, label: "Unexpected Private Key Structure")
{:error, :unexpected_private_key_structure}
end
rescue
e ->
IO.inspect("Failed to decrypt: #{inspect({e, __STACKTRACE__})}",
label: "Decryption Error"
)
{:error, e}
end
end
def ds5 do
try do
private_key_pem = File.read!("priv/flows/private_unencrypted.pem")
# Remove PEM headers and decode base64
private_key_der =
private_key_pem
|> String.replace(~r/-----BEGIN RSA PRIVATE KEY-----/, "")
|> String.replace(~r/-----END RSA PRIVATE KEY-----/, "")
|> Base.decode64!()
# Decode DER-encoded private key
{:Ok, private_key, _} = :public_key.der_decode(private_key_der, :RSAPrivateKey)
IO.inspect(private_key, label: "RSA Private Key Structure")
m = %{
encrypted_aes_key:
"i/j13zUEy37M00eFJYchyIyk+HIlmLs8X6gmJLO9UfiljUmQCX1kYVPYfVRbS+5moWKtkIc0K/KG59CObmT8sMbhSXFEbnCKL7bbLka80NC5lqoill4LKrAtOaWJ/Zxbw7YjWS1+zinkIZZCbJ0OGZjH24XVgx3pVx5PYK53JDSYt6T6kUo1Kt15uc5M7zne1/T46c5YCw2BnSGDvXX+74W7T8xo+dTl6krOzFGuxNXFOYtG3coFK3Ad4eteMIhlsxpoRjFggfIWv9VuA7TT9AOQ9mdF2juN98Hu0hm3kSi5IHHGagTq+UwrspnMl76kw+GZXinjQNSI2ZV8yYYHxQ==",
encrypted_flow_data:
"esYLI3l/ZREnkg3EDoipH/TeF8fL6goV1GELcV/Jv+WWVkB/5JcJ5NRoFzfnDZrTKA==",
initial_vector: "65SPDGDmgHlG4MiRzy5abQ=="
}
# Decrypt AES key
encrypted_aes_key = Base.decode64!(m.encrypted_aes_key)
decrypted_aes_key =
:crypto.private_decrypt(:rsa, encrypted_aes_key, private_key,
rsa_padding: :rsa_pkcs1_oaep_padding,
oaep_hash: :sha256
)
IO.inspect(decrypted_aes_key, label: "Decrypted AES Key")
decrypted_aes_key
rescue
e ->
IO.inspect("Failed to decrypt: #{inspect({e, __STACKTRACE__})}",
label: "Decryption Error"
)
{:error, e}
end
end
I’m at a loss for the errors I keep getting:
FL.ds4
Private Key Entry: {:PrivateKeyInfo,
<<48, 130, 4, 190, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1,
5, 0, 4, 130, 4, 168, 48, 130, 4, 164, 2, 1, 0, 2, 130, 1, 1, 0, 162, 144,
53, 31, 174, 226, 155, 127, 205, 227, ...>>, :not_encrypted}
Unexpected Private Key Structure: {:PrivateKeyInfo,
<<48, 130, 4, 190, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1,
5, 0, 4, 130, 4, 168, 48, 130, 4, 164, 2, 1, 0, 2, 130, 1, 1, 0, 162, 144,
53, 31, 174, 226, 155, 127, 205, 227, ...>>, :not_encrypted}
{:error, :unexpected_private_key_structure}
15:36:33.174 DB "get_inactive_agents"
15:36:33.193 DB "summarize_1"
15:36:33.227 DB "summarize_2"
15:36:33.423 {:sse, "stats-refresh"}
FL.ds5
Decryption Error: "Failed to decrypt: {%ArgumentError{message: \"non-alphabet character found: \\\"-\\\" (byte 45)\"}, [{Base, :bad_character!, 1, [file: ~c\"lib/base.ex\", line: 137]}, {Base, :\"-decode64base!/2-lbc$^0/2-0-\", 2, [file: ~c\"lib/base.ex\", line: 642]}, {Base, :decode64base!, 2, [file: ~c\"lib/base.ex\", line: 640]}, {FL, :ds5, 0, [file: ~c\"lib/chatflow/fl.ex\", line: 222]}, {:elixir, :eval_external_handler, 3, [file: ~c\"src/elixir.erl\", line: 386]}, {:erl_eval, :do_apply, 7, [file: ~c\"erl_eval.erl\", line: 750]}, {:elixir, :eval_forms, 4, [file: ~c\"src/elixir.erl\", line: 364]}, {Module.ParallelChecker, :verify, 1, [file: ~c\"lib/module/parallel_checker.ex\", line: 112]}]}"
{:error, %ArgumentError{message: "non-alphabet character found: \"-\" (byte 45)"}}
Any help and insight would be appreciated
Also, i have updated the question to include the requirements from META:
Implementing Endpoints for Flows - WhatsApp Flows (facebook.com)
Request Decryption and Encryption
The incoming request body is encrypted, you need to decrypt it first, then you need to encrypt the server response before returning it to the client.You can find code examples of decryption/encryption in various programming languages in the Code Examples section.
For data_api_version “3.0” you should follow below instructions to decrypt request payload:
extract payload encryption key from encrypted_aes_key field:
decode base64-encoded field content to byte array;
decrypt resulting byte array with the private key corresponding to the uploaded public key using RSA/ECB/OAEPWithSHA-256AndMGF1Padding algorithm with SHA256 as a hash function for MGF1;
as a result, you’ll get a 128-bit payload encryption key.
decrypt request payload from encrypted_flow_data field:
decode base64-encoded field content to get encrypted byte array;
decrypt encrypted byte array using AES-GCM algorithm, payload encryption key and initialization vector passed in initial_vector field (which is base64-encoded as well and should be decoded first). Note that the 128-bit authentication tag for the AES-GCM algorithm is appended to the end of the encrypted array.
result of above step is UTF-8 encoded clear request payload.
For data_api_version “3.0” you should follow below instructions to encrypt the response:encode response payload string to response byte array using UTF-8;
prepare initialization vector for response encryption by inverting all bits of the initialization vector used for request payload encryption;
encrypt response byte array using AES-GCM algorithm with the following parameters:
secret key - payload encryption key from request decryption stage;
initialization vector for response encryption from above step;
empty AAD (additional authentication data) - many libraries assume this by default, check the documentation of the library in use;
128-bit (16 byte) length for authentication tag - many libraries assume this by default, check the documentation of the library in use;
append authentication tag generated during encryption to the end of the encryption result;
encode the whole output as base64 string and send it in the HTTP response body as plain text.