You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:使用Perl验证Amazon Alexa请求签名的实现问题

Hey there! Let’s work through this Alexa signature verification snag in Perl together— I’ve tackled this exact scenario before, so I can walk you through the common pitfalls and working code to get you up and running.

First, let’s recap the core Alexa signature verification steps (you might be missing one!)

Before diving into the public key extraction, it’s critical to remember Alexa’s full validation flow— skipping any step can lead to failed checks:

  • Validate that the SignatureCertChainUrl comes from a trusted Amazon domain (*.amazontrust.com) over HTTPS (port 443) to avoid malicious certificate spoofing
  • Download and verify the entire certificate chain (not just the primary cert) to ensure it’s rooted in Amazon’s official root CA
  • Extract the public key from the primary certificate
  • Verify the request body’s signature using this public key (Alexa uses SHA-256 hashing now, so don’t forget that!)
  • Validate the request timestamp to prevent replay attacks (it should be within ±15 minutes of current UTC time)
Fixing public key extraction in Perl

The most common issues with extracting the public key from $primary_cert are related to PEM format handling or using the wrong module. Here are two reliable approaches:

Option 1: Use Crypt::OpenSSL::X509 (requires OpenSSL)

This is the most straightforward option if you have OpenSSL installed on your server. First install the module:

cpanm Crypt::OpenSSL::X509 Crypt::OpenSSL::RSA MIME::Base64

Then use this code to parse the certificate and extract the public key:

use strict;
use warnings;
use Crypt::OpenSSL::X509;
use Crypt::OpenSSL::RSA;
use MIME::Base64;

# Assume $primary_cert is the full PEM-formatted primary certificate (including BEGIN/END lines)
my $x509 = Crypt::OpenSSL::X509->new_from_string($primary_cert, Crypt::OpenSSL::X509::FORMAT_PEM());

# Extract the public key in PEM format
my $pub_key_pem = $x509->pubkey();

# Prepare the RSA object for signature verification (use SHA-256 as required by Alexa)
my $rsa = Crypt::OpenSSL::RSA->new_public_key($pub_key_pem);
$rsa->use_sha256_hash();

# Verify the signature: $signature is base64-encoded from the request header, $request_body is raw unmodified body
my $decoded_signature = decode_base64($signature);
my $is_valid = $rsa->verify($request_body, $decoded_signature);

if ($is_valid) {
    print "Signature is valid!\n";
} else {
    print "Signature verification failed— check certificate or request body integrity!\n";
}

Option 2: Use Crypt::X509 (pure Perl, no OpenSSL dependency)

If you can’t install OpenSSL-based modules, use this pure-Perl alternative. Install first:

cpanm Crypt::X509 Crypt::RSA MIME::Base64

Code example:

use strict;
use warnings;
use Crypt::X509;
use Crypt::RSA;
use MIME::Base64;

my $x509 = Crypt::X509->new(cert => $primary_cert);
if ($x509->error) {
    die "Failed to parse certificate: " . $x509->error;
}

# Convert the raw DER public key to PEM format
my $pub_key_der = $x509->pubkey();
my $pub_key_pem = "-----BEGIN PUBLIC KEY-----\n" . 
                  encode_base64($pub_key_der) . 
                  "-----END PUBLIC KEY-----";

# Verify the signature with SHA-256
my $rsa = Crypt::RSA->new();
my $pub_key = $rsa->import_key(Key => $pub_key_pem, Format => 'PEM');
my $verification_result = $rsa->verify(
    Message => $request_body,
    Signature => decode_base64($signature),
    Key => $pub_key,
    Hash => 'SHA256'
);

if ($verification_result) {
    print "Signature validated successfully!\n";
} else {
    die "Invalid signature: " . $rsa->errstr;
}
Critical pitfalls to avoid
  • Don’t skip certificate chain validation: Just checking the primary cert isn’t enough— you need to ensure it links back to Amazon’s root CA (e.g., Amazon Root CA 1). For Crypt::OpenSSL::X509, you can use the verify() method with a trusted root CA bundle.
  • Keep the request body intact: The signature is generated against the raw, unmodified request body. Use do { local $/; <STDIN> } to read the entire body without altering any characters.
  • Validate the SignatureCertChainUrl: Always check that the URL uses HTTPS, port 443, and ends with .amazontrust.com— this prevents attackers from feeding you a malicious certificate.
  • Check the timestamp: Alexa includes a Timestamp header— reject any requests where this timestamp is more than 15 minutes off from current UTC time to stop replay attacks.
Full simplified verification workflow example

Here’s a condensed version of the end-to-end process you can adapt for your web service:

use strict;
use warnings;
use Crypt::OpenSSL::X509;
use Crypt::OpenSSL::RSA;
use MIME::Base64;
use URI;
use HTTP::Tiny;
use DateTime;
use DateTime::Format::ISO8601;

# Grab request headers and body
my $signature = $ENV{'HTTP_SIGNATURE'};
my $cert_chain_url = $ENV{'HTTP_SIGNATURECERTCHAINURL'};
my $timestamp = $ENV{'HTTP_TIMESTAMP'};
my $request_body = do { local $/; <STDIN> };

# 1. Validate timestamp
my $req_time = DateTime::Format::ISO8601->parse_datetime($timestamp);
my $now = DateTime->now(time_zone => 'UTC');
if (abs($now->delta_ms($req_time)->in_units('minutes')) > 15) {
    die "Request timestamp is too old or far in the future";
}

# 2. Validate certificate URL
my $uri = URI->new($cert_chain_url);
unless ($uri->scheme eq 'https' && $uri->port == 443 && $uri->host =~ /\.amazontrust\.com$/) {
    die "Invalid certificate chain URL";
}

# 3. Download certificate chain
my $http = HTTP::Tiny->new();
my $cert_response = $http->get($cert_chain_url);
die "Failed to download cert chain: " . $cert_response->{reason} unless $cert_response->{success};
my @certs = split /-----END CERTIFICATE-----\s*/, $cert_response->{content};
my $primary_cert = $certs[0] . "-----END CERTIFICATE-----";

# 4. Parse primary cert and verify signature
my $x509 = Crypt::OpenSSL::X509->new_from_string($primary_cert, Crypt::OpenSSL::X509::FORMAT_PEM());
my $pub_key_pem = $x509->pubkey();
my $rsa = Crypt::OpenSSL::RSA->new_public_key($pub_key_pem);
$rsa->use_sha256_hash();

my $decoded_sig = decode_base64($signature);
if ($rsa->verify($request_body, $decoded_sig)) {
    # Handle valid request here
    print "Content-Type: application/json\r\n\r\n";
    print '{"version":"1.0","response":{"outputSpeech":{"type":"PlainText","text":"Hello from your verified Alexa service!"}}}';
} else {
    # Reject invalid request
    print "HTTP/1.1 403 Forbidden\r\n";
    print "Content-Type: text/plain\r\n\r\n";
    print "Invalid request signature";
}

内容的提问来源于stack exchange,提问作者chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:15:09