Создание подписи AWS Signature v4 для загрузки на s3 (миграция с v2)

В настоящее время у меня есть рабочая реализация, которая работает следующим образом:

Пользовательский интерфейс выбирает файл => click upload => вызывает мой бэкэнд-API, чтобы запросить подпись, так как я не хочу показывать свой доступ + secretkey => вернуть подпись + policy => сделать загрузку на s3.

Это прекрасно работает и денди для v2.

String base64Policy = (new BASE64Encoder()).encode(policy.toString().getBytes("UTF-8")).replaceAll("\n", "").replaceAll("\r", "");

        Mac hmac = Mac.getInstance("HmacSHA1");
        hmac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1"));
        String signature = (new BASE64Encoder()).encode(hmac.doFinal(base64Policy.getBytes("UTF-8"))).replaceAll("\n", "");

Теперь я дошел до забавного момента, когда мои новые сегменты находятся в регионе, где v2 не поддерживается.

Я следил за документацией AWS, но мне кажется, что я немного неправильно понимаю полезную нагрузку. Мне действительно нужно, чтобы мой пользовательский интерфейс передавал хэш sha256 всего моего файла? Поскольку это может показаться чем-то вроде боли, тем более что мои файлы могут быть> 1 гигабайта.

Код, который я пытался использовать:

        byte[] signatureKey = getSignatureKey(secretKey, LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")),  bucketRegion, "s3");
        StringBuilder sb = new StringBuilder();
        for (byte b : signatureKey) {
            sb.append(String.format("%02X", b));
        }

private static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
        byte[] kDate = HmacSHA256(dateStamp, kSecret);
        byte[] kRegion = HmacSHA256(regionName, kDate);
        byte[] kService = HmacSHA256(serviceName, kRegion);
        byte[] kSigning = HmacSHA256("aws4_request", kService);
        return kSigning;
    }



private static byte[] HmacSHA256(String data, byte[] key) throws Exception {
        String algorithm="HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF8"));
    }

Но это дает неверный ответ подписи, когда я пытаюсь использовать остальную часть моего кода.

Неужели я так сильно схожу с ума и просто недоразумение: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html?

Любая помощь будет принята с благодарностью, так как я слишком долго держу голову на этом, и я предпочел бы не слишком много ремонтировать.

1 ответ

Решение

Вы можете загрузить файл на S3, используя стандартные методы SDK без генерации подписи, см. Документацию. Но если вам нужна подпись по какой-то причине, я думаю, самый простой способ сгенерировать подпись - это использовать методы из AWS SDK, см. Следующий класс, который расширяет AWS4Signer:

public class AwsAuthUtil extends AWS4Signer {
    private String serviceName;
    private AWSCredentials credentials;
    private String region;

    public AwsAuthUtil(AWSCredentials credentials, String region, String serviceName) {
        this.credentials = credentials;
        this.region = region;
        this.serviceName = serviceName;
    }

    public String getSignature(String policy, LocalDateTime dateTime) {
        try {
            String dateStamp = dateTime.format(ofPattern("yyyyMMdd"));
            return Hex.encodeHexString(hmacSha256(newSigningKey(credentials, dateStamp, region, serviceName), policy));
        } catch (Exception e) {
            throw new RuntimeException("Error", e);
        }
    }

    private byte[] hmacSha256(byte[] key, String data) throws Exception {
        Mac mac = Mac.getInstance(SigningAlgorithm.HmacSHA256.name());
        mac.init(new SecretKeySpec(key, SigningAlgorithm.HmacSHA256.name()));
        return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }
}

где AWS4Signer из

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.213</version>
</dependency>

а также AWSCredentials может быть построен как

AWSCredentials awsCredentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);

Также вы должны учитывать заголовки http, когда вы используете многокомпонентные данные, например, смотрите следующий метод, который собирает HttpEntity

public HttpEntity buildPostMultipartDataEntity(String objectKey, byte[] data, String signature, LocalDateTime dateTime) {

    String dateTimeStr = dateTime.format(ofPattern("yyyyMMdd'T'HHmmss'Z'"));
    String date = dateTime.format(ofPattern("yyyyMMdd"));

    return MultipartEntityBuilder
        .create()
        .addTextBody("key", objectKey)
        .addTextBody("Policy", policy)
        .addTextBody("X-Amz-Signature", signature)
        .addTextBody("X-Amz-Algorithm", algorithm)
        .addTextBody("X-Amz-Date", dateTimeStr)
        .addTextBody("X-Amz-Credential", String.format("%s/%s/%s/s3/aws4_request", accessKey, date, region))
        .addBinaryBody("file", data)
        .build();
}
Другие вопросы по тегам