광고 네트워크의 광고주 식별자 복호화

Authorized Buyers를 통해 광고를 채우기 위해 자바스크립트 태그를 사용하는 광고 네트워크는 Android 및 iOS 기기 모두에서 광고주 식별자를 수신할 수 있습니다. 이 정보는 Authorized Buyers에서 관리하는 자바스크립트 태그의 %%EXTRA_TAG_DATA%% 또는 %%ADVERTISING_IDENTIFIER%% 매크로를 통해 전송됩니다. 이 섹션의 나머지 부분에서는 %%EXTRA_TAG_DATA%% 추출을 중점적으로 다루지만, 유사한 방식으로 복호화할 수 있는 %%ADVERTISING_IDENTIFIER%% 암호화된 proto 버퍼 MobileAdvertisingId에 관한 자세한 내용은 IDFA 또는 광고 ID를 사용한 리마케팅을 참고하세요.

타임라인

  1. 광고 네트워크는 Authorized Buyers UI를 통해 자바스크립트 인앱 태그를 업데이트하고 아래와 같이 %%EXTRA_TAG_DATA%% 매크로에 추가합니다.
  2. 게재 시 앱에서 Google 모바일 광고 SDK를 통해 Authorized Buyers에 광고를 요청하면서 광고주 식별자를 안전하게 전달합니다.
  3. 앱은 자바스크립트 태그를 수신하며, 이때 해당 식별자가 포함된 암호화된 광고 네트워크 프로토콜 버퍼로 %%EXTRA_TAG_DATA%% 매크로가 채워집니다.
  4. 앱은 이 태그를 실행하여 낙찰된 광고에 대해 광고 네트워크를 호출합니다.
  5. 이 정보를 사용 (수익화)하려면 광고 네트워크에서 프로토콜 버퍼를 처리해야 합니다.
    1. WebSafeBase64를 사용하여 websafe 문자열을 바이트 문자열로 다시 디코딩합니다.
    2. 아래에 설명된 스키마를 사용하여 복호화합니다.
    3. proto를 역직렬화하고 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 20바이트 섹션의 직렬화된 ExtraTagData 객체
counter_bytes 섹션의 서수를 보여주는 바이트 값입니다. 아래를 참고하세요.
final_message %%EXTRA_TAG_DATA%% 매크로를 통해 전송된 총 바이트 배열입니다 (WebSafeBase64 인코딩 제외).
연산자 세부정보
hmac(key, data) SHA-1 HMAC: key를 사용하여 data를 암호화합니다.
a || b a 문자열은 b 문자열로 연결됩니다.

카운터_바이트 계산

counter_bytesciphertext에 있는 각 20바이트 섹션의 순서를 표시합니다. 마지막 섹션에는 1~20바이트가 포함될 수 있습니다. hmac() 함수를 실행할 때 counter_bytes를 올바른 값으로 채우려면 20바이트 섹션(나머지 포함)을 집계하고 다음 참조 테이블을 사용합니다.

섹션 번호 counter_bytes
0 없음
1 ... 256 1바이트 값은 0에서 255까지 순차적으로 증가합니다.
257 ... 512 2바이트 첫 번째 바이트 값은 0이고 두 번째 바이트 값은 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. 확인: 무결성 서명이 HMAC(integrity_key, byte_array || initialization_vector)의 4바이트를 전달합니다.

복호화 의사 코드

// 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%%에 전달된 데이터를 디코딩하고 복호화했다면 프로토콜 버퍼를 역직렬화하고 타겟팅에 사용할 광고주 식별자를 가져올 수 있습니다.

프로토콜 버퍼에 익숙하지 않은 경우 이 문서부터 시작하세요.

정의

Google의 광고 네트워크 프로토콜 버퍼는 다음과 같이 정의됩니다.

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로 모바일 앱 인벤토리 타겟팅을 참고하세요.

자바 라이브러리

광고 네트워크의 광고주 식별자를 인코딩 및 디코딩하는 암호화 알고리즘을 구현하는 대신 DoubleClickCrypto.java를 사용할 수 있습니다. 자세한 내용은 암호화를 참고하세요.