S3のチェックサム機能を試してみた

# 背景

年末に[AWS Certified DevOps Engineer - Professional](https://aws.amazon.com/jp/certification/certified-devops-engineer-professional/)の更新試験を控えていて、再勉強中です。そんな中、オンライン教材の模擬試験の問題にS3へアップロードしたオブジェクトのチェックに関する問題がありました。
[公式ページ](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/checking-object-integrity.html)もあり、試験対策的には以下の注意点を確認しておけばよさそうでした。

:::note alert
AWS Management Console を使用していくつかの操作を実行するとき、オブジェクトのサイズが 16 MB より大きい場合、Amazon S3 はマルチパートアップロードを使用します。この場合、チェックサムは完全なオブジェクトの直接チェックサムではなく、個々のパートのチェックサム値に基づく計算です。
・・後略・・
:::

しかし、これ以外にもアルゴリズムの差異とかアップロード方法などによって色々違うようです。

去年の投稿もS3に関係した「[S3ブロックパブリックアクセスって結局どう使いこなすべき？](https://qiita.com/etnk/items/70b7da6a39f7c2a4464e)」でしたが、今年もS3に関するチェックサムを掘り下げてみたいと思いました。

自分は資料を読んだだけではナレッジが頭に入らないタイプです。実際にやってみて理解しておこうと思いました。

# チェックサム復習

[チェックサム - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%82%B5%E3%83%A0)を簡単にまとめると、**何らかの法則で、実データの値を加算して算出。実データとセットで提供して実データが正しいかの検証に使うチェック文字列**という事になると思います。

## 身近な使用例

### ダウンロードファイル

[commons-langライブラリのダウンロードページ](https://commons.apache.org/proper/commons-lang/download_lang.cgi)では、各ファイルに対応するsha512形式チェックサムがあります。

### コンテナイメージ

[Dockerのドキュメント](https://docs.docker.jp/engine/reference/commandline/images.html)によると、dockerイメージの指定時にタグ名ではなく、より厳密に`image@digest`形式で指定する事が出来る様になっています。`digest 値`という名称ですが、sha256形式のチェックサムの様です。

## チェックサム作成方法

`python`には`hashlib`という便利なライブラリがあるようなので、それを利用してみます。

Google Colaboratoryで`generate_checksum`というプロンプトでAI生成するとそのライブラリを使った以下の関数作ってくれました。他のコード補完AIでも作ってくれると思います。便利な世の中です。

```python
# prompt: def generate_checksum

import hashlib

def generate_checksum(filename, hash_function='sha256'):
    """
    Generates the checksum of a file using the specified hash function.

    Args:
        filename: The path to the file.
        hash_function: The hash function to use (default: 'sha256').
                      Supported functions: 'md5', 'sha1', 'sha256', 'sha512'.

    Returns:
        The hexadecimal representation of the checksum, or None if an error occurs.
    """
    try:
        hash_obj = hashlib.new(hash_function)
        with open(filename, 'rb') as file:
            for chunk in iter(lambda: file.read(4096), b''):
                hash_obj.update(chunk)
        return hash_obj.hexdigest()
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found.")
        return None
    except ValueError:
        print(f"Error: Invalid hash function '{hash_function}'.")
        return None
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        return None
```

先のページからダウンロードしてきたcommons-lang3-3.17.0-bin.zipのsha512を作ると以下の文字列でした。
`00ea810d00b0f8a5bf898edfed1fa022e9bd2bf31c9c00859de2ac6c4bc971d6833f3c2e0b24cecad6bb979de2078be8b1cc7eb42ca63c9ec35f70b1490e8c3d`

同ページに記載していたsha512チェックサムは以下の通りでした。同じですね。
`00ea810d00b0f8a5bf898edfed1fa022e9bd2bf31c9c00859de2ac6c4bc971d6833f3c2e0b24cecad6bb979de2078be8b1cc7eb42ca63c9ec35f70b1490e8c3d`

# S3でのチェックサム使用（基本編）

## アップロードチェック

AWSコンソール上でS3にファイルをアップロードする際、プロパティにチェックサムを詳細に指定できる設定が確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/593898a8-fd8e-bda8-0da9-f063386b0171.png)

オフの時（デフォルト）では、MD5チェックサムがETagにセットされ、オンにすると事前に作ったチェックサムと関数を指定する事で整合性をチェックしてくれるようです。

でたらめな値を指定すると当然エラーになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/d63a966d-0b64-c951-d7e1-b0de406dfc14.png)

先ほどのcommons-lang3-3.17.0-bin.zipのsha256チェックサムを指定したのですが、エラーになりました。
`924d95a2792478457e59ef062e3d4b508febf60aef779b7c0a6a0e057c9e33a9`

一旦空白で登録してみます。追加後のオブジェクト詳細は以下の通りになってました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/4c8dbab1-308b-4ae5-5d28-bde47be8de48.png)

[公式記事](https://aws.amazon.com/jp/getting-started/hands-on/amazon-s3-with-additional-checksums/?ref=docs_gateway/amazons3/checking-object-integrity.html)によるとbase64エンコードが必要な様です。

以下sha256の16進数文字列をbase64エンコードする関数をAIに作ってもらって、変換した値を入力してアップロードしたら成功しました。**sha256チェックサムを単純文字列としてbase64エンコードするとダメ**みたいなので気を付けてください。

```python
# prompt: def encode_hexstr_to_base64

import base64

def encode_hexstr_to_base64(hex_string):
    """Encodes a hexadecimal string to a Base64 string.

    Args:
        hex_string: The hexadecimal string to encode.

    Returns:
        The Base64 encoded string, or None if an error occurs.
    """
    try:
        byte_string = bytes.fromhex(hex_string)
        base64_string = base64.b64encode(byte_string).decode('utf-8')
        return base64_string
    except ValueError:
        print("Error: Invalid hexadecimal string.")
        return None
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        return None
```

中途半端にS3にアップロードされる事態を防ぐためにはチェックサム活用が必須という事が解りました。

## ETagの確認

SHA256チェックサムでアップロードしたオブジェクトのETag（エンティティタグ）は以下の値でした。この値は、md5チェックサムを前述関数で作成した値（base64エンコードは無し）と一致しました。
`858c90de3dfc7b9740610d2ab0585c76`

## ダウンロードチェック

アップロード側では正しくアップロードされている保証ができる事になります。ダウンロード側のチェックを見ていきます。

ETagの値はs3apiで取得可能でした。

```console
$ aws s3api head-object --bucket hogehoge-bucket --key commons-lang3-3.17.0-bin.zip
{
    "AcceptRanges": "bytes",
    "Expiration": "expiry-date=\"Sun, 22 Dec 2024 00:00:00 GMT\", rule-id=\"hogehoge-bucket-rule\"",
    "LastModified": "2024-12-11T14:51:29+00:00",
    "ContentLength": 11055479,
    "ETag": "\"858c90de3dfc7b9740610d2ab0585c76\"",
    "ContentType": "application/zip",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}
```

追加チェックサムは以下のコマンドで取得可能でした。
```console
$ aws s3api get-object-attributes --bucket hogehoge-bucket --key commons-lang3-3.17.0-bin.zip --object-attributes Checksum
{
    "LastModified": "2024-12-11T14:51:29+00:00",
    "Checksum": {
        "ChecksumSHA256": "kk2VonkkeEV+We8GLj1LUI/r9grvd5t8CmoOBXyeM6k="
    }
}
```

# S3でのチェックサム使用（マルチパート編）

[公式ページ：マルチパートアップロードにパートレベルのチェックサムを使用する](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/checking-object-integrity.html#large-object-checksums)

正しくアップロード、ダウンロードされているかのチェックは巨大なファイルで使用するケースが多いと思います。しかし、冒頭の内容などによると、少なくともETagは巨大な16MBを超えるオブジェクトのチェックに使うのは難しそうです。ETagが使えなさそうではありますが、追加チェックサムでもやっぱり駄目なのか試したいと思います。

[amazon-corretto-17.0.13.11.1-windows-x64-jdk.zip](https://docs.aws.amazon.com/ja_jp/corretto/latest/corretto-17-ug/downloads-list.html)が180MBあるようなのでこちらで試したいと思います。

前述Google Colaboratory上での関数で算出された値を設定しようとしたところ・・・・・
SH256チェックサム：`eaacdc37c6f23753ebfae341985f11baacfaf84ca972e600803cb409c94ee8ca`
同、Base64エンコード：`6qzcN8byN1Pr+uNBmF8Ruqz6+EypcuYAgDy0CclO6Mo=`

なんと、追加チェックサムは16MB以上のオブジェクトでは設定できない様です。16MB以下のファイルでは入力可能だった事前計算された値の欄が入力不可能になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/57e45c2a-df37-70d9-bcf7-3c287d642fad.png)

# 重要情報

[[アップデート] Amazon S3 デフォルトのデータ整合性保護が変わります #AWSreinvent](https://dev.classmethod.jp/articles/amazon-s3-default-data-integrity-protections/)

どうやら、今後デフォルトチェックサムが変わっていくようです。クラスメソッドさんの記事によるとまだ有効になっていない様です。しばらくしたら自分も試してみたいと思います。
16MB超えるファイルに関してはこちらのページにも書いてあった通り、独自にメタデータに値をセットしてチェックするのがよさそうです。
> 上述のようなシチュエーションでも整合性チェックを行いたい場合は、PUT時に自前でチェックサムを計算した上でメタデータを付与し、GET時にメタデータのチェックサムとGETしたオブジェクトから生成したチェックサムと比較をする必要があります。

[公式ページ：s3api put-object](https://awscli.amazonaws.com/v2/documentation/api/2.7.12/reference/s3api/put-object.html)
で、`--metadata`オプションが確認できます。もしくはmetadataでなく、チェックサム値を別ファイルにしてアップロードしておく方が簡単かもしれません。