Macとは
Googleのサイトによると
https://cloud.google.com/kms/docs/mac-signatures?hl=ja
MAC 署名は、データの整合性と信頼性の検証に利用される暗号出力です。
とあります。そして
MAC 署名には主に 2 つの目的があります。
* 署名されたデータの整合性を検証します。
* メッセージの信頼性を確認する。
と説明されています。
また、こちらのサイトでは
メッセージ認証コード(MAC)とは、相手から届いたメッセージが途中で
改竄やすり替えに合っていないか検証するための短い符号。
送信者と受信者が共有する秘密のデータ(鍵)とメッセージ本体から生成する。
となっています。
ただ今回のサンプルでは、「共通鍵を渡していない相手のメッセージ」が改ざんされていないかを確認するという目的では使えません。(その場合は別の方法による電子署名を使います)
Javaで書いてみました
Tinkは、バージョンアップのたびに、APIが変わり、以前書いたコードが非推奨となることが多いため、その都度ドキュメントを見ながら修正しています。
以下に示すコードはtink-1.12.0.jarを用いています。依存ライブラリーとして protobuf-java と gson が必要です。
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