פענוח אותות Hyperlocal של הטירגוט

אם בעלי אתרים מעבירים ל-Authorized Buyers נתוני מיקום לנייד שהם ספציפיים יותר ממיקוד, הקונים המורשים ישלחו גידור גיאוגרפי של קונים בשדה מוצפן חדש: BidRequest.encrypted_hyperlocal_set.

ציר הזמן

  1. משתמש מתקין אפליקציה לנייד שנתמכת על ידי מודעות, ומביע הסכמה לאפליקציה בגישה למיקום המכשיר ושיתוף שלו עם צדדים שלישיים. האפליקציה הזו גם משולבת ב-Google Ads SDK ושולחת את מיקום המכשיר אל Google.
  2. השרתים של Google מייצרים אות טירגוט מקומי ספציפי במיוחד שמייצג את גדר הגבול הגיאוגרפי סביב מיקום המכשיר, למשל כדי להגן על פרטיות המשתמש.
  3. שרתי Google מסדרים ולהצפין את אות הטירגוט ההיפר-מקומי באמצעות מפתח האבטחה הספציפי לכל קונה. לידיעתך, מגיש הצעת המחיר מסתמך על אותו מפתח כדי לפענח את המאקרו WINNING_PRICE.
  4. מגיש הצעת המחיר מפענח ומבטלים סידור מחדש של אות הטירגוט ההיפר-מקומי למאגר פרוטוקול. לאחר מכן מגיש הצעת המחיר יכול לנתח את האות ולבחור הצעת מחיר בהתאם.

יחסי תלות

צריכה להיות לכם ספריית הצפנה שתומכת ב-HMAC של SHA-1, כמו Openssl.

הגדרה

אות טירגוט ספציפי למיקום מוגדר בפרוטוקול הזה:

// A hyperlocal targeting location when available.
//
message Hyperlocal {
  // A location on the Earth's surface.
  //
  message Point {
    optional float latitude = 1;
    optional float longitude = 2;
  }

  // The mobile device can be at any point inside the geofence polygon defined
  // by a list of corners.  Currently, the polygon is always a parallelogram
  // with 4 corners.
  repeated Point corners = 1;
}

message HyperlocalSet {
  // This field currently contains at most one hyperlocal polygon.
  repeated Hyperlocal hyperlocal = 1;

  // The approximate geometric center of the geofence area.  It is calculated
  // exclusively based on the geometric shape of the geofence area and in no
  // way indicates the mobile device's actual location within the geofence
  // area. If multiple hyperlocal polygons are specified above then
  // center_point is the geometric center of all hyperlocal polygons.
  optional Hyperlocal.Point center_point = 2;
}

// Hyperlocal targeting signal when available, encrypted as described at
// https://developers.google.com/authorized-buyers/rtb/response-guide/decrypt-hyperlocal
optional bytes encrypted_hyperlocal_set = 40;

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

  • קו הרוחב וקו האורך של כל פינה במצולע ברצף, עוברים כשדה חוזר corners.
  • המרכז הגיאומטרי המשוער של אזור הגבול הווירטואלי, שעבר בשדה האופציונלי center_point.

המבנה של אות מיקוד

אות הטירגוט הנפרד המקומי המוצפן שנמצא ב-BidRequest.encrypted_hyperlocal_set מכיל 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 אובייקט HyperlocalSet סידורי, בקטעים של 20 בייט.
counter_bytes ערך בייטים שמראה את המספר הסידורי של הקטע, כמפורט למטה.
final_message מערך בייטים שנשלח באמצעות השדה BidRequest.encrypted_hyperlocal_set.
מפעילים פרטים
hmac(key, data) SHA-1 HMAC, באמצעות key להצפנת data.
a || b המחרוזת a משורשרת עם המחרוזת b.

חישוב בייטים נגדיים

counter_bytes מסמן את הסדר של כל קטע של 20 בייט ב-ciphertext. שימו לב שהקטע האחרון עשוי להכיל בין 1 ל-20 בייט, כולל. כדי למלא את הערך counter_bytes בערך הנכון כשמריצים את הפונקציה hmac(), יש לספור את הקטעים ב-20 בייט (כולל השאר) ולהשתמש בטבלת העזר הבאה:

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

אנחנו לא צופים שהאורך של BidRequest.encrypted_hyperlocal_set יעלה על קילובייט אחד, גם אם אנחנו לוקחים בחשבון צמיחה נוספת. עם זאת, counter_bytes יכול להיות באורך הנדרש כדי לתמוך באות טירגוט היפר-מקומי של אורך שרירותי.

סכמת הצפנה

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

  1. סנכרון סידורי: האות ההיפר-מקומי של הטירגוט, שהוא מופע של האובייקט HyperlocalSet כפי שהוגדר בפרוטו, עובר עיבוד ראשון דרך SerializeAsString() למערך בייט.

  2. הצפנה: לאחר מכן, מערך הבייטים מוצפן באמצעות סכימת הצפנה מותאמת אישית שנועדה לצמצם את תקורת הגודל תוך הקפדה על אבטחה הולמת. בסכמת ההצפנה נעשה שימוש באלגוריתם HMAC חשוב כדי ליצור לוח סודי המבוסס על initialization_vector, שהוא ייחודי לאירוע החשיפה.

פסאודו-הצפנה של הצפנה

byte_array = SerializeAsString(HyperlocalSet 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)

שכפול פענוח

(initialization_vector, ciphertext, integrity_signature) = final_message // split up according to length rules
pad = hmac(encryption_key, initialization_vector || counter_bytes)  // for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad // for each 20-byte section of ciphertext
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');
    }
  }
}

דוגמה לאות ומפתחות היפר-מקומיים

כדי לבדוק ולאמת את הקוד:

  1. ממירים מחרוזת שמכילה 308 תווים הקסדצימליים למערך של 154 בייטים. לדוגמה, בהתאם למחרוזת הבאה:
    E2014EA201246E6F6E636520736F7572636501414243C0ADF6B9B6AC17DA218FB50331EDB376701309CAAA01246E6F6E636520736F7572636501414243C09ED4ECF2DB7143A9341FDEFD125D96844E25C3C202466E6F6E636520736F7572636502414243517C16BAFADCFAB841DE3A8C617B2F20A1FB7F9EA3A3600256D68151C093C793B0116DB3D0B8BE9709304134EC9235A026844F276797
    
    ממירה אותו למערך בנפח 154 בייט באופן הבא:
    const char serialized_result[154] = { 0xE2, 0x01, 0x4E, ... };
    
  2. יש לקרוא לשיטה BidRequest.ParsePartialFromString() כדי להסיר את מערך ה-154 בייט למאגר פרוטוקול BidRequest.
    BidRequest bid_req;
    bid_req.ParsePartialFromString(serialzed_result);
    
  3. מוודאים שיש ב-BidRequest רק שלושה שדות:
    • encrypted_hyperlocal_set
      בוצעה הצהרה בהודעה BidReqeust.
    • encrypted_advertising_id
      בוצעה הצהרה בהודעה BidReqeust.Mobile.
    • encrypted_hashed_idfa
      בוצעה הצהרה בהודעה BidReqeust.Mobile.

    למשל:

    encrypted_hyperlocal_set:(
        {  100,  100 },
        {  200, -300 },
        { -400,  500 },
        { -600, -700 },)
    encrypted_advertising_id: { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }
    encrypted_hashed_idfa : { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0xF1 }
    
  4. משתמשים בשדות encryption_key ו-integrity_key הבאים כדי לפענח את 3 השדות ולאמת שהם מפענחים אותם נכון.
    encryption_key = {0x02, 0xEE, 0xa8, 0x3c, 0x6c, 0x12, 0x11, 0xe1, 0x0b,
        0x9f, 0x88, 0x96, 0x6c, 0xee, 0xc3, 0x49, 0x08, 0xeb, 0x94, 0x6f, 0x7e,
        0xd6, 0xe4, 0x41, 0xaf, 0x42, 0xb3, 0xc0, 0xf3, 0x21, 0x81, 0x40};
    
    integrity_key = {0xbf, 0xFF, 0xec, 0x55, 0xc3, 0x01, 0x30, 0xc1, 0xd8,
        0xcd, 0x18, 0x62, 0xed, 0x2a, 0x4c, 0xd2, 0xc7, 0x6a, 0xc3, 0x3b, 0xc0,
        0xc4, 0xce, 0x8a, 0x3d, 0x3b, 0xbd, 0x3a, 0xd5, 0x68, 0x77, 0x92};
    

זיהוי של מתקפות תגובה לא עדכניות

כדי לזהות מתקפות של תגובות מיושןות, אנחנו ממליצים לסנן תגובות עם חותמת זמן שונה באופן משמעותי מתקופת המערכת, אחרי להביא בחשבון הבדלים בין אזורי זמן. השרתים שלנו מוגדרים לזמן PST/PDT.

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

ספריית Java

במקום להטמיע את האלגוריתמים של הקריפטו לצורך קידוד ופענוח של אותות הטירגוט המקומי, אפשר להשתמש ב- DoubleClickCrypto.Java. למידע נוסף, ראו קריפטוגרפיה.