רשתות מודעות שמשתמשות ב
תגי JavaScript למילוי מודעות דרך Authorized Buyers יכולות לקבל מזהי מפרסמים גם במכשירי Android וגם במכשירי iOS.
המידע נשלח באמצעות המאקרו %%EXTRA_TAG_DATA%%
או %%ADVERTISING_IDENTIFIER%%
בתג JavaScript שמנוהל על ידי Authorized Buyers. בהמשך הקטע הזה נעסוק בחילוץ
%%EXTRA_TAG_DATA%%
, אבל ניתן לעיין במאמר
רימרקטינג באמצעות IDFA או מזהה פרסום
כדי לקבל פרטים על
מאגר המידע הזמני של %%ADVERTISING_IDENTIFIER%%
המאגר המוצפן
MobileAdvertisingId
, שאפשר לפענח במקביל.
ציר הזמן
- רשת המודעות מעדכנת את תגי JavaScript בתוך האפליקציה דרך ממשק המשתמש של Authorized Buyers,
ומוסיפה את פקודת המאקרו
%%EXTRA_TAG_DATA%%
כפי שמוסבר בהמשך. - בזמן הצגת המודעה, האפליקציה מבקשת מודעה מ-Authorized Buyers באמצעות Google Mobile Ads SDK, תוך העברה מאובטחת של מזהה המפרסם.
- האפליקציה מקבלת בחזרה את תג ה-JavaScript, ופקודת המאקרו
%%EXTRA_TAG_DATA%%
ממולאת במאגר הנתונים הזמני של הפרוטוקול של רשת המודעות, שמכיל את המזהה הזה. - האפליקציה מפעילה את התג הזה, ומבצעת קריאה לרשת המודעות עבור המודעה הזוכה.
- כדי להשתמש במידע הזה (לייצר ממנו הכנסות), רשת המודעות צריכה לעבד את מאגר הפרוטוקול:
- מפענחים את המחרוזת websafe בחזרה ל-bytestring באמצעות WebSafeBase64.
- פענח אותו באמצעות הסכמה המתוארת בהמשך.
- מבצעים פעולת deserialize של האב ומשיגים את מזהה המפרסם מ-ExtraTagData.advertising_id או מ-ExtraTagData.hashed_idfa.
יחסי תלות
- את המקודד WebSafeBase64.
- ספריית הצפנה שתומכת ב-SHA-1 HMAC, כמו Openssl.
- מהדר של מאגר אחסון לפרוטוקולים של Google.
פענוח מחרוזת websafe
מכיוון שהמידע שנשלח באמצעות המאקרו %%EXTRA_TAG_DATA%%
חייב להישלח דרך כתובת URL, השרתים של Google מקודדים אותו באמצעות base64 שבטוח לשימוש באינטרנט (RFC 3548).
לכן, לפני שתנסו לבצע פענוח, תצטרכו לפענח את תווי ה-ASCII בחזרה למחרוזת בייט (bytestring). הקוד לדוגמה C++ שבהמשך מבוסס על BIO_f_base64() של Project OpenSSL(), והוא חלק מקוד הפענוח לדוגמה של 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); }
המבנה של 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
המקורי, ה-ciphertext
התואם לו בגודל 20 בייטים נוצר באופן הבא:
<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 | בייט אחד. הערך גדל מ-0 ל-255 ברצף. |
257 ... 512 | 2 בייט. הערך של הבייט הראשון הוא 0, כלומר הערך של הבייט השני מתפתח ברציפות מ-0 ל-255. |
513 ... 768 | 3 בייט. הערך של שני הבייטים הראשונים הוא 0, כלומר, הערך של כל בייטים ברצף מ-0 ל-255. |
סכמת הצפנה
סכימת ההצפנה מבוססת על אותה הסכמה שמשמשת לפענוח אות הטירגוט ההיפר-מקומי.
סידור טורי: המופע של האובייקט ExtraTagData, כפי שמוגדר במאגר הפרוטוקולים, עובר סריאליזציה תחילה דרך
SerializeAsString()
למערך בייטים.הצפנה: המערך הבייטים מוצפן באמצעות סכמת הצפנה מותאמת אישית, שנועדה לצמצם את תקורת הגודל תוך הקפדה על אבטחה מספקת. בסכימת ההצפנה נעשה שימוש באלגוריתם 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) לאמת את ביטים של התקינות באמצעות מפתח התקינות. המפתחות יסופקו לכם במהלך הגדרת החשבון. אין הגבלות על מבנה ההטמעה. בדרך כלל, אתם אמורים להיות מסוגלים לקחת את הקוד לדוגמה ולהתאים אותו לצרכים שלכם.
- יוצרים את ה-pad:
HMAC(encryption_key, initialization_vector || counter_bytes)
- XOR: לוקחים את התוצאה הזו ואת
<xor>
עם המידע מוצפן כדי להפוך את ההצפנה. - אימות: חתימת התקינות מעבירה 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; }
צריך לבצע פעולת deserial שלו באמצעות ParseFromString()
, כפי שמתואר
בתיעוד של מאגר הנתונים הזמני של C++.
לפרטים על השדות 'advertising_id
' ו-'hashed_idfa
' ב-Android, קראו את המאמר פענוח מזהה פרסום ומלאי שטחי פרסום באפליקציות לנייד לפי IDFA.
ספריית Java
במקום להטמיע את האלגוריתמים של הקריפטו כדי לקודד ולפענח את מזהי המפרסם לרשתות של מודעות, אפשר להשתמש ב- DoubleClickCrypto.java. למידע נוסף קראו את המאמר קריפטוגרפיה.