【Java初心者】Google Tinkを使ったMAC(メッセージ認証コード)

# Macとは

Googleのサイトによると
https://cloud.google.com/kms/docs/mac-signatures?hl=ja

```
MAC 署名は、データの整合性と信頼性の検証に利用される暗号出力です。
```
とあります。そして
```
MAC 署名には主に 2 つの目的があります。
* 署名されたデータの整合性を検証します。
* メッセージの信頼性を確認する。
```
と説明されています。

また、こちらのサイトでは

https://e-words.jp/w/%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E8%AA%8D%E8%A8%BC%E3%82%B3%E3%83%BC%E3%83%89.html

```
メッセージ認証コード（MAC）とは、相手から届いたメッセージが途中で
改竄やすり替えに合っていないか検証するための短い符号。
送信者と受信者が共有する秘密のデータ（鍵）とメッセージ本体から生成する。
```
となっています。

ただ今回のサンプルでは、「共通鍵を渡していない相手のメッセージ」が改ざんされていないかを確認するという目的では使えません。(その場合は別の方法による電子署名を使います)

# Javaで書いてみました

Tinkは、バージョンアップのたびに、APIが変わり、以前書いたコードが非推奨となることが多いため、その都度ドキュメントを見ながら修正しています。

以下に示すコードはtink-1.12.0.jarを用いています。依存ライブラリーとして protobuf-java と gson が必要です。

```java
package sample2024;

import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeyTemplates;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.Mac;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.mac.MacConfig;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Base64;

public class MacTest {

    /**
     * plainTextの内容が変更されていないか認証コードを作り あとで検証する
     *
     * @param args
     */
    public static void main(String[] args) {
        String plainText = "こんにちは"; // もとのテキスト
        try {
            System.out.println("Mac秘密鍵を生成");
            MacConfig.register();
            KeysetHandle privateMacKeysetHandle = KeysetHandle.generateNew(KeyTemplates.get("HMAC_SHA256_128BITTAG"));
            String serializedmacKeyset = TinkJsonProtoKeysetFormat.serializeKeyset(
                    privateMacKeysetHandle, InsecureSecretKeyAccess.get()); // MAC秘密鍵(JSON)

            // MAC秘密鍵をローカルに保存する場合(非推奨)
            //  System.out.println("Mac秘密鍵を保存");
            //  String macKey = "mac_priv.key";
            //  Path macFile = Paths.get(macKey);
            //  Files.write(macFile, serializedmacKeyset.getBytes(UTF_8));
            // MACコードを生成するには 平文、MAC秘密鍵が必要
            String macData = getMac(plainText, serializedmacKeyset);
            System.out.println("MAC-DATA:" + macData);
            // 同じテキストで試します。なお、違う文字列で試すと検証が失敗します
            String plainText1 = plainText; 
            // 検証する場合は 検証対象のデータ、MAC秘密鍵 , MACコードが必要)
            boolean verMac = verMac(plainText1, serializedmacKeyset, macData);
            System.out.println("Mac検証:" + verMac);
        } catch (IOException | GeneralSecurityException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * Mac(メッセージ認証コード)出力
     *
     * @param plaintext 平文
     * @param macKey 共通鍵
     * @return Macコード(Base64)
     */
    static String getMac(String plaintext, String macKey) throws GeneralSecurityException, IOException {
        KeysetHandle handle = CleartextKeysetHandle.read(JsonKeysetReader.withString(macKey));
        Mac macPrimitive = handle.getPrimitive(Mac.class);
        byte[] macTag = macPrimitive.computeMac(plaintext.getBytes());
        return new String(Base64.getEncoder().encode(macTag));
    }

    /**
     * Mac(メッセージ認証コード)検証
     *
     * @param plaintext 平文
     * @param macKey 共通鍵
     * @param macfile MACコード
     * @return 正しく検証できればtrue
     */
    static boolean verMac(String plaintext, String macKey, String macfile) {
        try {
            KeysetHandle keysetHandle = CleartextKeysetHandle.read(
                    JsonKeysetReader.withString(macKey));
            Mac mac = keysetHandle.getPrimitive(Mac.class);
            mac.verifyMac(Base64.getDecoder().decode(macfile), plaintext.getBytes());
            return true;
        } catch (IOException | GeneralSecurityException e) {
            System.out.println(e.getMessage());
            return false;
        }
    }
}
```

# 実行結果

```
Mac秘密鍵を生成
MAC-DATA:AeBy8Mmaf6Bx1JJHUBeKNvpUCi5T
Mac検証:true
```
検証できない(もとのデータとは異なっている)場合は以下のようになります
```
Mac秘密鍵を生成
MAC-DATA:AeBy8Mmaf6Bx1JJHUBeKNvpUCi5T
invalid MAC
Mac検証:false
```