PowerShellでS3のEtagを計算してみる（暗号化なし＆SSE-S3&マルチパートアップロード対応）

- [EtagをつかってS3にアップロードしたファイルの整合性をたしかめてみる（マルチパートアップロードなし）](https://qiita.com/SAITO_Keita/items/29cf207b969537b2b68b)
- [EtagをつかってS3にアップロードしたファイルの整合性をたしかめてみる（マルチパートアップロードあり）](https://qiita.com/SAITO_Keita/items/21f9b5390c3487ac9957)
- [AWSマネジメントコンソールからS3にマルチパートアップロードした時の分割サイズについて](https://qiita.com/SAITO_Keita/items/99691f77b00b07a193bf)

上記のようにS3にアップロードされた際の、Eタグについて3つほど記事を投稿しました。
S3ファイルのEタグについてまとめると下記のようになっていました。

| 方式       | マルチパートアップロード | Eタグの値                                                                        |
| ---------- | ------------------------ | -------------------------------------------------------------------------------- |
| 暗号化なし | なし                     | MD5 ダイジェスト                                                                 |
| 暗号化なし | あり                     | 分割したファイルごとにMD5を計算。計算された値を連結。連結した値のMD5ダイジェスト |
| sse-s3     | なし                     | MD5 ダイジェスト                                                                 |
| sse-s3     | あり                     | 分割したファイルごとにMD5を計算。計算された値を連結。連結した値のMD5ダイジェスト |
| sse-kms    | なし                     | MD5 ダイジェストではない（詳細は公開されていない（多分））                           |
| sse-kms    | あり                     | MD5 ダイジェストではない（詳細は公開されていない（多分））                           |
| sse-c      | なし                     | MD5 ダイジェストではない（詳細は公開されていない（多分））                           |
| sse-c      | あり                     | MD5 ダイジェストではない（詳細は公開されていない（多分））                           |

マルチパートアップロードではない場合は、MD5を計算すればよいのですが。
マルチパートアップロードの場合は、分割されたファイルごとに個別に計算する必要があり、以前、bashで実装された計算ツールを紹介しました（[antespi/s3md5](https://github.com/antespi/s3md5)）。

今回、Eタグの値についてPowerShellで計算するスクリプトを書いてみたので紹介します。

```powershell
#!/usr/bin/env pwsh

<#
.SYNOPSIS
    S3のEtag計算ツール
.DESCRIPTION
    S3のEtagは暗号化なし、もしくはSSE-S3の場合のみEtagの計算形式については公開されている
    このため暗号化なし、SSE-S3の場合はEtagの値について計算できますが。
    SSE-KMS , SSE-CについてはEtagについて情報が多分公開されていないため値は計算できません。
.EXAMPLE
    PS > .\Get-FileEtag.ps1 -Path .\testfile -ChunkByteSize 8mb
    任意のファイルを分割サイズ 8mb で計算
.EXAMPLE
    PS > .\Get-FileEtag.ps1 -Path .\foobar -UploadFromAWSManagementConsole
    マネジメントコンソールからアップロードしたファイルの場合
#>

[cmdletbinding()]
param (
    [Parameter(mandatory = $true)]
    [ValidateScript({
            if ( -Not (Test-Path -Path $_ -PathType Leaf) ) {
                throw "file is not exist:$_"
            }
            return $true
        })]
    [System.IO.FileInfo]$Path,
    [Parameter(mandatory = $false)]
    [ValidateScript({
            # 5MiBより小さい、5GiBより大きい場合はエラー
            if ( $_ -lt 5MB -or $_ -gt 5GB ) {
                throw "ChunkByteSize is 5 MB to 5 GB"
            }
            return $true
        })]
    [int64]$ChunkByteSize = 8MB,
    [Parameter(mandatory = $false)]
    [Switch]$UploadFromAWSManagementConsole
)

PROCESS {

    Set-StrictMode -Version Latest
    $ErrorActionPreference = "stop"

    $absolutepath = Resolve-Path $Path

    if ($UploadFromAWSManagementConsole) {
        # マネジメントコンソールからアップロードされた場合はchunksize 17179870 byte
        $target_chunk_byte = 17179870
    }
    else {
        $target_chunk_byte = $ChunkByteSize
    }

    Write-Host $("calculation targets is {0}" -f $absolutepath)
    Write-Host $("ChunkByteSize is {0} byte" -f $target_chunk_byte)

    [byte[]]$hash_array = New-Object system.Array[] 0

    # 対象ファイルを読み込み
    [byte[]]$bytestream = [System.IO.File]::ReadAllBytes($absolutepath)

    # 分割数を算出
    $split_count = [math]::ceiling($bytestream.Length / $target_chunk_byte)

    $cursor = 0

    # 各分割ファイルごとにMD5を計算
    # splib_byte ごとに で split_count -1 回 MD5を計算
    for ($i = 1; $i -lt $split_count; $i++) {
        $new_entry = New-Object -TypeName byte[] -ArgumentList $target_chunk_byte
        # 対象ファイルから分割部分を抽出
        [System.Array]::copy($bytestream, $cursor, $new_entry, 0 , $target_chunk_byte)
        # 抽出した部分のMD5を計算
        $hash_part = [System.Security.Cryptography.HashAlgorithm]::Create("MD5").ComputeHash($new_entry)
        $hash_array += $hash_part
        # カーソルをシフト
        $cursor = $i * $target_chunk_byte
    }

    # 最終ファイルのみsplib_byte以下のファイルサイズ
    $lastfilesize = $bytestream.Length - ($split_count - 1) * $target_chunk_byte
    $new_entry = New-Object -TypeName byte[] -ArgumentList $lastfilesize
    # 対象ファイルから最終分割部分を抽出
    [System.Array]::copy($bytestream, $cursor, $new_entry, 0 , $lastfilesize)
    # 抽出した部分のMD5を計算
    $hash_part = [System.Security.Cryptography.HashAlgorithm]::Create("MD5").ComputeHash($new_entry)
    $hash_array += $hash_part

    if ($split_count -gt 1 ) {
        # ファイルごとに計算して加算した値からmd5を取得
        $hash_string = $([System.Security.Cryptography.HashAlgorithm]::Create("MD5").ComputeHash($hash_array) | ForEach-Object { $_.ToString("x2") }) -join ""
        # 末尾に分割数を追加
        $etag = "$hash_string-{0}" -f $split_count
    }
    else {
        # 分割なし、計算したMD5
        $hash_string = ($hash_array | ForEach-Object { $_.ToString("x2") }) -join ""
        $etag = $hash_string
    }

    Write-Host $("Etag is {0}" -f $etag)
}

```

## マルチパートアップロード時の分割サイズについて補足

aws-cliからファイルをマルチパートアップロードするさいの個々のファイルサイズについて、`multipart_chunksize`で設定されており、このデフォルト値は8MB（8388608 Byte）となります。

[AWS CLI S3 Configuration](https://docs.aws.amazon.com/cli/latest/topic/s3-config.html#multipart-threshold)

またブラウザからマネジメントコンソールを使ってアップロードする際は、17179870 Byteとなります。
詳細は下記記事参照。

[AWSマネジメントコンソールからS3にマルチパートアップロードした時の分割サイズについて](https://qiita.com/SAITO_Keita/items/99691f77b00b07a193bf)

## 使い方

### aws-cliから8mb分割でsse-s3でアップロードしたファイルの場合

```powershell
.\Get-FileEtag.ps1 -Path .\100mb
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/2a5a0a11-6c9d-d5ef-99cd-09d6b8f9ba2c.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/739d0561-0940-f9f2-d7f2-4fcb4c35507e.png)


### aws-cliから16mb分割でsse-s3でアップロードしたファイルの場合

```powershell
.\Get-FileEtag.ps1 -Path .\100mb -ChunkByteSize 16mb
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/428d5e29-4025-155f-3b6c-55253c088138.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/945d8808-4eb8-2a76-26bb-e2b9399c5d8e.png)

### マネジメントコンソールからアップロードした場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/01dc469d-9f80-60cc-099e-7bb6718b213e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1670622/a2ef9546-5569-d481-e825-e289d1c4846c.png)

## 総評

S3のEtagでのファイル整合性チェック、調べ始めてみると。

暗号化の有無、マルチパートアップロードの有無、chunksize（aws-cliのデフォルト値8mb、マネジメントコンソールの値（17179870 Byte）と色々あり。

要素によって値が異なり、また暗号化の形式によってはEtagをファイルの整合性確認に利用できない事がわかりました。

いろいろと気をつけるポイントありますね。

個人的にはファイルの整合性確認するのには、Etagではなく2022年に追加されたチェックサムアルゴリズムを素直に利用するのがよさそうに思いました。