פענוח מזהי מפרסמים עבור רשתות מודעות

רשתות מודעות שמשתמשות ב- תגי JavaScript למילוי מודעות דרך Authorized Buyers עומדים בדרישות לקבל מזהי מפרסמים גם במכשירי Android וגם במכשירי iOS. המידע נשלח באמצעות %%EXTRA_TAG_DATA%% או מאקרו %%ADVERTISING_IDENTIFIER%% בתג JavaScript מנוהל על ידי Authorized Buyers. החלק הבא מתמקד בחילוץ %%EXTRA_TAG_DATA%% אבל לראות רימרקטינג עם IDFA או מזהה פרסום לפרטים נוספים במאגר הנתונים הזמני של Proto המוצפן %%ADVERTISING_IDENTIFIER%% MobileAdvertisingId שאפשר לפענח אותו באופן מקביל.

ציר הזמן

  1. רשת המודעות מעדכנת את תגי ה-JavaScript באפליקציות שלה דרך ממשק המשתמש של Authorized Buyers, מוסיפים את המאקרו %%EXTRA_TAG_DATA%% כמו שמוסבר בהמשך.
  2. בזמן הצגת המודעות, האפליקציה מבקשת להציג מודעה מ-Authorized Buyers דרך Google Mobile Ads SDK, או העברה מאובטחת של מזהה המפרסם.
  3. האפליקציה מקבלת בחזרה את תג ה-JavaScript, עם הערך %%EXTRA_TAG_DATA%% מלא את המאקרו במאגר הנתונים הזמני של פרוטוקול רשת המודעות המוצפן, שמכיל את המזהה הזה.
  4. האפליקציה מפעילה את התג הזה ומבצעת קריאה לרשת המודעות של המודעה.
  5. כדי להשתמש במידע הזה (לייצר הכנסות) ממנו, רשת המודעות צריכה לעבד מאגר הנתונים הזמני של הפרוטוקול:
    1. מפענחים את המחרוזת websafe חזרה ל-bytestring באמצעות WebSafeBase64.
    2. פרשו אותה באמצעות הסכמה המתוארת בהמשך.
    3. מבצעים פעולת deserialing של הפרוטו ומשיגים את מזהה המפרסם מה: ExtraTagData.reporting_id או ExtraTagData.hashed_idfa.

יחסי תלות

  1. פלטפורמת WebSafeBase64 המקודד.
  2. ספריית הצפנה שתומכת ב-SHA-1 HMAC, כמו Openssl.
  3. פרוטוקול Google באמצעות מהדר במאגרי נתונים זמניים.

פענוח המחרוזת ל-websafe

כי המידע נשלח דרך פקודת המאקרו %%EXTRA_TAG_DATA%% צריך להישלח דרך כתובת URL, ושרתי Google מקודדים אותה באמצעות base64 בטוח לאינטרנט (RFC 3548).

לפני הניסיון לכן צריך לפענח את תווי ה-ASCII בחזרה bytestring. קוד 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<char*>(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);
}

המבנה של bytestring מוצפן

אחרי שתפענחו את תווי ה-ASCII בחזרה ל-bytestring, תוכלו להתחיל כדי לפענח אותו. ה-bytestring המוצפן מכיל 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 מסמן את הסדר של כל קטע באורך 20 בייטים ciphertext. שימו לב שהקטע האחרון עשוי להכיל בין 1 ל- 20 בייטים, כולל. כדי למלא את counter_bytes בערך הנכון כשמריצים את הפונקציה hmac(), סופרים את הקטעים באורך 20 בייטים (כולל השאר) ומשתמשים בטבלת העזר הבאה:

מספר החלק ערך של counter_bytes
0 ללא
1 ... 256 1 בייט. הערך גדל מ-0 ל-255 ברצף.
257 ... 512 2 בייטים. הערך של הבייט הראשון הוא 0, הערך של הבייט השני עולה מ-0 ל-255 ברצף.
513 ... 768 3 בייטים. הערך של שני הבייטים הראשונים הוא 0, הערך של הבייט האחרון עולה מ-0 ל-255 ברצף.

חזרה למעלה

סכימת הצפנה

סכימת ההצפנה מבוססת על אותה סכימה שמשמשת לפענוח אות טירגוט ספציפי למיקום.

  1. סריאליזציה: מופע של האובייקט ExtraTagData בתור המוגדר במאגר הנתונים הזמני של הפרוטוקולים עובר סריאליזציה באמצעות SerializeAsString() למערך בייטים.

  2. Encryption: מערך הבייטים מוצפן לאחר מכן באמצעות סכימת הצפנה מותאמת אישית שנועדה למזער את תקורת הגודל, תוך הקפדה על אבטחה הולמת. סכימת ההצפנה משתמשת באלגוריתם 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> עם מידע מוצפן (ciphertext) כדי להפוך את ההצפנה.
  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%%, אפשר לבצע פעולת deserialize לשטח האחסון הזמני של הפרוטוקול ולקבל את מזהה המפרסם לטירגוט.

אם אתם לא מכירים את השימוש במאגרי נתונים זמניים בפרוטוקולים, מתחילים עם התיעוד שלנו.

הגדרה

מאגר הפרוטוקול של רשת מודעות שלנו מוגדר כך:

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;
}

צריך לבצע פעולת deserialize באמצעות ParseFromString() כמו שמתואר מסמכי תיעוד של מאגר הנתונים הזמני של פרוטוקול C++.

לפרטים על advertising_id Android ועל iOS hashed_idfa שדות, לעיון בפענוח מזהה פרסום וטירגוט לאפליקציה לנייד מלאי שטחי פרסום עם IDFA.

ספריית Java

במקום להטמיע את האלגוריתמים לקריפטו כדי לקודד ולפענח במזהי המפרסמים לרשתות של מודעות, אפשר להשתמש DoubleClickCrypto.java. מידע נוסף זמין במאמר הבא: קריפטוגרפיה.