広告ネットワークの広告主 ID の復号

JavaScript タグを使用して認定バイヤーから広告を配信する広告ネットワークでは、Android デバイスと iOS デバイスの両方で広告主 ID を受け取ることができます。この情報は、認定バイヤーが管理する JavaScript タグの %%EXTRA_TAG_DATA%% または %%ADVERTISING_IDENTIFIER%% マクロを介して送信されます。このセクションの残りの部分では %%EXTRA_TAG_DATA%% を抽出することに重点を置いていますが、同じように復号できる %%ADVERTISING_IDENTIFIER%% 暗号化されたプロトコル バッファ MobileAdvertisingId について詳しくは、 IDFA または広告 ID によるリマーケティングをご覧ください。

タイムライン

  1. 広告ネットワークでは、認定バイヤーの管理画面から JavaScript アプリ内タグを更新し、%%EXTRA_TAG_DATA%% マクロを追加します(下記の説明を参照)。
  2. 配信時、アプリは Google Mobile Ads SDK を介して認定バイヤーに広告をリクエストし、同時に広告主の識別子は安全に渡します。
  3. アプリは JavaScript タグを受信します。%%EXTRA_TAG_DATA%% マクロには、その識別子を含む暗号化された広告ネットワーク プロトコル バッファが入力されています。
  4. アプリはこのタグを実行し、落札広告の広告ネットワークを呼び出します。
  5. この情報を使用(収益化)するには、広告ネットワークがプロトコル バッファを処理する必要があります。
    1. WebSafeBase64 を使用して、ウェブセーフの文字列をバイト文字列にデコードします。
    2. 以下に概説するスキームを使用して復号します。
    3. プロトコルを逆シリアル化し、ExtraTagData.advertising_id または ExtraTagData.hashed_idfa から広告主 ID を取得します。

依存関係

  1. WebSafeBase64 エンコーダ
  2. SHA-1 HMAC をサポートする暗号ライブラリ(Openssl など)。
  3. Google のプロトコル バッファ コンパイラ

ウェブセーフ文字列をデコードする

%%EXTRA_TAG_DATA%% マクロで送信される情報は URL 経由で送信する必要があるため、Google のサーバーはこれをウェブセーフな Base64(RFC 3548)でエンコードします。

したがって、復号を行う前に、ASCII 文字をバイト文字列にデコードする必要があります。以下の C++ コードの例は、OpenSSL プロジェクトの BIO_f_base64() に基づくものであり、Google の復号コード例の一部です。

string AddPadding(const string& b64_string) {
  if (b64_string.size() % 4 == 3) {
    return b64_string + "=";
  } else if (b64_string.size() % 4 == 2) {
    return b64_string + "==";
  }
  return b64_string;
}

// Adapted from http://www.openssl.org/docs/man1.1.0/crypto/BIO_f_base64.html
// Takes a web safe base64 encoded string (RFC 3548) and decodes it.
// Normally, web safe base64 strings have padding '=' replaced with '.',
// but we will not pad the ciphertext. We add padding here because
// openssl has trouble with unpadded strings.
string B64Decode(const string& encoded) {
  string padded = AddPadding(encoded);
  // convert from web safe -> normal base64.
  int32 index = -1;
  while ((index = padded.find_first_of('-', index + 1)) != string::npos) {
    padded[index] = '+';
  }
  index = -1;
  while ((index = padded.find_first_of('_', index + 1)) != string::npos) {
    padded[index] = '/';
  }

  // base64 decode using openssl library.
  const int32 kOutputBufferSize = 256;
  char output[kOutputBufferSize];

  BIO* b64 = BIO_new(BIO_f_base64());
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  BIO* bio = BIO_new_mem_buf(const_cast(padded.data()),
                             padded.length());
  bio = BIO_push(b64, bio);
  int32 out_length = BIO_read(bio, output, kOutputBufferSize);
  BIO_free_all(bio);
  return string(output, out_length);
}

暗号化されたバイト文字列の構造

ASCII 文字をバイト文字列にデコードしたら、復号できます。暗号化されたバイト文字列には、次の 3 つのセクションが含まれます。

  • initialization_vector: 16 バイト。
  • ciphertext: 一連の 20 バイトのセクション。
  • integrity_signature: 4 バイト。
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

ciphertext バイト配列は、複数の 20 バイトのセクションに分割されます。ただし、最後のセクションには 1 ~ 20 バイトの範囲が含まれます。元の byte_array の各セクションに対して、対応する 20 バイトの ciphertext が次のように生成されます。

<byte_array <xor> HMAC(encryption_key, initialization_vector || counter_bytes)>

ここで、|| は連結です。

定義

変数 詳細
initialization_vector 16 バイト - インプレッションに固有の値。
encryption_key 32 バイト - アカウント設定時に提供されます。
integrity_key 32 バイト - アカウント設定時に提供されます。
byte_array シリアル化された ExtraTagData オブジェクト(20 バイトのセクション)。
counter_bytes セクションの序数を示すバイト値。下記をご覧ください。
final_message %%EXTRA_TAG_DATA%% マクロによって送信されたバイト配列の合計(WebSafeBase64 エンコードを差し引いた値)。
演算子 詳細
hmac(key, data) SHA-1 HMAC。key を使用して data を暗号化します。
a || b 文字列 a が文字列 b と連結されます。

count_bytes を計算する

counter_bytes は、ciphertext の各 20 バイト セクションの順序をマークします。最後のセクションには、1 ~ 20 バイトが含まれている可能性があります。hmac() 関数の実行時に counter_bytes に正しい値を入力するには、20 バイトのセクション(余りを含む)を数え、次の参照表を使用します。

セクション番号 counter_bytes
撮影していない なし
1 ... 256 1 バイトです。値は 0 ~ 255 で順次増分します。
257 ~ 512 2 バイトです。最初のバイトの値は 0、2 番目のバイトの値は 0 から 255 まで順次増加します。
513 ~ 768 3 バイトです。最初の 2 バイトの値は 0 で、最後のバイトの値は 0 から 255 まで順次増加します。

トップへ戻る

暗号化スキーム

この暗号化スキームは、ハイパーローカル ターゲティング信号の復号に使用されるものと同じスキームに基づいています。

  1. シリアル化: プロトコル バッファで定義されている ExtraTagData オブジェクトのインスタンスは、最初に SerializeAsString() によってバイト配列にシリアル化されます。

  2. 暗号化: 次に、バイト配列は、適切なセキュリティを確保しながらサイズのオーバーヘッドを最小限に抑えるよう設計されたカスタム暗号化スキームを使用して暗号化されます。この暗号化スキームでは、鍵付き HMAC アルゴリズムを使用して、インプレッション イベントに固有の initialization_vector に基づいてシークレット パッドを生成します。

暗号化疑似コード

byte_array = SerializeAsString(ExtraTagData object)
pad = hmac(encryption_key, initialization_vector ||
      counter_bytes )  // for each 20-byte section of byte_array
ciphertext = pad <xor> byte_array // for each 20-byte section of byte_array
integrity_signature = hmac(integrity_key, byte_array ||
                      initialization_vector)  // first 4 bytes
final_message = initialization_vector || ciphertext || integrity_signature

復号スキーム

復号コードは、1)暗号鍵を使用してプロトコル バッファを復号し、2)整合性キーで整合性ビットを検証する必要があります。鍵は、アカウント設定時に提供されます。実装の構成方法に制限はありません。ほとんどの場合、サンプルコードは必要に応じて調整できます。

  1. パッドを生成: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: この結果と <xor> を暗号テキストに置き換えて、暗号化を元に戻します。
  3. 検証: 完全性署名は 4 バイトの HMAC(integrity_key, byte_array || initialization_vector) を渡します。

復号疑似コード

// split up according to length rules
(initialization_vector, ciphertext, integrity_signature) = final_message

// for each 20-byte section of ciphertext
pad = hmac(encryption_key, initialization_vector || counter_bytes)

// for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad

confirmation_signature = hmac(integrity_key, byte_array ||
                         initialization_vector)
success = (confirmation_signature == integrity_signature)

サンプル C++ コード

ここには、完全な復号のサンプルコードの鍵関数が含まれています。

bool DecryptByteArray(
    const string& ciphertext, const string& encryption_key,
    const string& integrity_key, string* cleartext) {
  // Step 1. find the length of initialization vector and clear text.
  const int cleartext_length =
     ciphertext.size() - kInitializationVectorSize - kSignatureSize;
  if (cleartext_length < 0) {
    // The length cannot be correct.
    return false;
  }

  string iv(ciphertext, 0, kInitializationVectorSize);

  // Step 2. recover clear text
  cleartext->resize(cleartext_length, '\0');
  const char* ciphertext_begin = string_as_array(ciphertext) + iv.size();
  const char* const ciphertext_end = ciphertext_begin + cleartext->size();
  string::iterator cleartext_begin = cleartext->begin();

  bool add_iv_counter_byte = true;
  while (ciphertext_begin < ciphertext_end) {
    uint32 pad_size = kHashOutputSize;
    uchar encryption_pad[kHashOutputSize];

    if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
              encryption_key.length(), (uchar*)string_as_array(iv),
              iv.size(), encryption_pad, &pad_size)) {
      printf("Error: encryption HMAC failed.\n");
      return false;
    }

    for (int i = 0;
         i < kBlockSize && ciphertext_begin < ciphertext_end;
         ++i, ++cleartext_begin, ++ciphertext_begin) {
      *cleartext_begin = *ciphertext_begin ^ encryption_pad[i];
    }

    if (!add_iv_counter_byte) {
      char& last_byte = *iv.rbegin();
      ++last_byte;
      if (last_byte == '\0') {
        add_iv_counter_byte = true;
      }
    }

    if (add_iv_counter_byte) {
      add_iv_counter_byte = false;
      iv.push_back('\0');
    }
  }

広告ネットワーク プロトコル バッファからデータを取得する

%%EXTRA_TAG_DATA%% で渡されたデータをデコードして復号したら、プロトコル バッファのシリアル化を解除して、ターゲティング用の広告主 ID を取得できます。

プロトコル バッファに不慣れな場合は、まずドキュメントをご覧ください。

定義

広告ネットワーク プロトコル バッファは次のように定義されます。

message ExtraTagData {
  // advertising_id can be Apple's identifier for advertising (IDFA)
  // or Android's advertising identifier. When the advertising_id is an IDFA,
  // it is the plaintext returned by iOS's [ASIdentifierManager
  // advertisingIdentifier]. For hashed_idfa, the plaintext is the MD5 hash of
  // the IDFA.  Only one of the two fields will be available, depending on the
  // version of the SDK making the request.  Later SDKs provide unhashed values.
  optional bytes advertising_id = 1;
  optional bytes hashed_idfa = 2;
}

C++ プロトコル バッファのドキュメントで説明されているように、ParseFromString() を使用してシリアル化を解除する必要があります。

Android の advertising_id および iOS の hashed_idfa フィールドについて詳しくは、広告 ID を復号するIDFA を使用してモバイルアプリ広告枠をターゲットに設定するをご覧ください。

Java ライブラリ

広告ネットワーク用の広告主 ID をエンコードおよびデコードする暗号アルゴリズムを実装する代わりに、 DoubleClickCrypto.java を使用できます。詳細については、暗号化をご覧ください。