微信原圖到底在救人,還是在泄露隱私?取決于你怎么用!
一. 序
最近又看到有人在討論,微信原圖泄露隱私的事情。起因好像是有人在社交媒體上發(fā)布了一張照片,然后被網(wǎng)友定位到具體的生活范圍,甚至直接定位到居住的小區(qū),樓層門牌號(hào)等信息,想想還是很可怕的。
在這個(gè)事件中的「網(wǎng)友定位」過程,其實(shí)很大一部分信息來自照片內(nèi)容本身泄露的信息。各種警匪片大家應(yīng)該也看過,專業(yè)人士可以通過一張照片分析出很多有用的信息,例如從窗戶陽光的曬入角度,就可以分析出房間的朝向,有一些鏡面的反光,也可以分析拍照環(huán)境的更多信息等等。
但這里說到微信原圖暴露的信息,其實(shí)就是我們拍照時(shí),在照片中攜帶的 Exif 信息。這個(gè) Exif 信息是相機(jī)在拍照時(shí),專門記錄的一些屬性信息和拍攝數(shù)據(jù)。例如 GPS 定位數(shù)據(jù)、拍攝時(shí)間、拍攝時(shí)相機(jī)的方向等。
Exif 信息只是拍攝圖片文件的固有信息,不光微信原圖分享,其實(shí)你通過任何形式,將源文件分享出去,都是會(huì)攜帶這些信息。但其中 GPS 涉及到定位,而定位信息又是一個(gè)在某些人看來,比較「敏感」的信息。
為什么說是某些人呢?圖片攜帶的定位信息,暴露出去確實(shí)可能會(huì)造成困擾,但是某些時(shí)刻,這又是可以救命的,例如《民警巧用圖片定位,解救被騙 CX 組織受害人》之類的新聞,我想大家應(yīng)該也看過。另外也有很多人反映,不使用原圖會(huì)導(dǎo)致接收到的圖片模糊(被壓縮),這也是一部分人偏愛發(fā)送「原圖」的原因。
一邊確實(shí)有隱私保護(hù)的需求,另一邊也有發(fā)送無損原圖的需要。微信是怎么解決的呢?在發(fā)送圖片的時(shí)候,提供了一個(gè)「原圖」的選項(xiàng),將是否發(fā)送圖片源文件的選擇權(quán),交到了用戶自己的手里。值得一提的是,微信發(fā)送圖片,為了傳輸效率以及隱私的考慮,默認(rèn)是發(fā)送壓縮后的圖片的,在壓縮的過程中,會(huì)清理 EXIF 信息,所以如果你找不到「原圖」的選項(xiàng),那么就說明這里只支持發(fā)送「壓縮圖」,例如微信朋友圈。
到這就清晰了,所謂微信原圖泄露的隱私,只是圖片在拍攝時(shí)存入的固有信息 EXIF,而只要通過處理 EXIF 信息就會(huì)被抹去。所以如果想對(duì)方接收到 EXIF 信息,就發(fā)送原圖,反之則可以通過壓縮處理一下再發(fā)送。
本來到這里就算完了,不過作為一個(gè)技術(shù)向的公眾號(hào),我們?cè)倮^續(xù)科普一下照片 EXIF 信息以及在 Android 開發(fā)時(shí)如何讀取和修改它。
二. 圖片的 EXIF
2.1 什么是 Exif?
Exif(Exchangeable image file format)表示可交換圖像文件格式,是專門記錄數(shù)碼相機(jī)拍照時(shí)的一些參數(shù),例如定位信息、拍攝設(shè)備方向、曝光、色彩等信息,并將這些信息寫入圖片文件中,以保證在傳輸時(shí)保留這些信息。
Exif 可以被附加到 JPEG、TIFF、RIFF 等文件中,簡單來說,圖片文件中有一塊特殊的區(qū)域,可以存放一些額外的 Exif 信息,以保證我們?cè)谑褂脠D片時(shí)更方便。
這本身沒什么壞處,除了 GPS 定位信息會(huì)讓我們敏感之外,其他信息更多的是為了輔助我們使用。例如前面提到的拍攝照片時(shí),設(shè)備的方向信息,就可以保證無論我們拍照時(shí),手機(jī)的方向是側(cè)著的或者是倒著的,拍照后在相冊(cè)中預(yù)覽時(shí),永遠(yuǎn)保持正著的原因。
一般的圖片處理軟件,都可以讀取出圖片的 Exif 信息,并且支持修改。隨便找一個(gè)在線查看 Exif 信息的工具網(wǎng)站,就可以查看到我上傳圖片的 Exif 信息。
圖片的 Exif 信息,沒有任何的保護(hù),我們可以對(duì)其任意的修改。但是這些信息你也看到了,沒有修改的意義,通常就是壓縮時(shí)直接抹去。
2.2 壓縮會(huì)損失 Exif
壓縮會(huì)損失掉 Exif 信息,這也就是為什么在不發(fā)送原圖的時(shí)候,接收者是讀取不到 Exif 信息的。這很好理解,以現(xiàn)在圖片手機(jī)攝像頭支持的像素來說,一張大畫幅的照片,隨隨便便就是上十 MB 甚至幾十 MB,這對(duì)發(fā)送的網(wǎng)絡(luò)和存儲(chǔ)都是有壓力的,所以通常 App 的做法是在發(fā)送前,本地做一次圖片壓縮。
前面提到,Exif 信息會(huì)記錄拍照時(shí),拍照設(shè)備持握的方向,例如有時(shí)我們會(huì)將手機(jī)倒過來拍全身照。這照片在相冊(cè)展示時(shí),永遠(yuǎn)都是正確的方向,我不會(huì)得到一個(gè)頭朝下的照片。
如果壓縮會(huì)導(dǎo)致 Exif 丟失,為什么壓縮后的圖片,在顯示時(shí)依然可以保證顯示方向的正確?
這就要說到圖片壓縮時(shí)的策略,我們就拿 Android 下比較出名的開源圖片壓縮庫 Luban 舉例,Github 上很多圖片壓縮庫都是借鑒或者引用它來實(shí)現(xiàn)的。
在 Luban 的 Engine.java 文件中,可以找到相關(guān)的代碼,邏輯很簡單,就是在壓縮前,先將圖片按照 Exif 中記錄的方向,旋轉(zhuǎn)后再進(jìn)行處理。
這也就是為什么圖片壓縮時(shí),雖然抹去了 Exif 信息,但是圖片顯示的方向依然是正確的原因。
2.3 壓縮后保留 Exif 信息
保留 Exif 信息這種需求,我確實(shí)想不到有什么場(chǎng)景需要在壓縮后,保留此信息的。
但是如果有必要的話,最簡單的處理方式,就是在壓縮前,將圖片的 Exif 讀取存儲(chǔ),壓縮后再寫入圖片中。
那么這就又涉及到,我們?nèi)绾尉幋a讀取和寫入圖片的 Exif 信息。
三. 操作 Exif 信息
3.1 使用 ExifInterface
在 Android 中,需要 ExifInterface 來讀取 Exif 信息,如果你直接在 AS 中搜索這個(gè)類,可以發(fā)現(xiàn)在 android.media 包下,確實(shí)有一個(gè) ExifInterface,但是我不建議使用它。
自從 Android Support 25.1.0 開始,又添加了一個(gè)新的支持庫:ExifInterface。這是由于 Android 7.1 對(duì) ExifInterface 做了重大修改,因此建議使用此 Support 包,它最低支持到 Api 9+。
隨著 AndroidX 的發(fā)布,對(duì) ExifInterface 也做了遷移支持,只不過現(xiàn)在的版本還是 Beta01。
使用方式并沒有什么太大的差異,完全取決于你項(xiàng)目的要求,這里舉例就使用最新的 28.+ 了
- api "com.android.support:exifiinterface:28.+"
Support 包和 android.media 中的 ExifInterface 基本操作,都是類似的,都提供了對(duì)指定圖片的 Exif 信息進(jìn)行讀寫的功能,區(qū)別在于 Support 包中包含了 140 多個(gè)不同的屬性,而其中近 100 個(gè)是 android 7.1 中新增的。
3.2 獲取 ExifInterface
ExifInterface 存在兩個(gè)構(gòu)造函數(shù),可以傳遞一個(gè)圖片文件路徑或者圖片的 InputStream。
上面兩種構(gòu)造方式,都可以獲取到一個(gè) ExifInterface 對(duì)象。它們之間有些差異:
1、使用 InputStream 獲得的 ExifInterface 無法被修改,而直接讀取的圖片文件,則可以修改。
2、ExifInterface 無法處理遠(yuǎn)端的 InputStream,例如是從 HttpURIConnection 返回的輸入流,所以這里建議使用 content:// 或者file:// 這種 Uri 路徑。
3.3 讀取 Exif 信息
獲得 ExifInterface 對(duì)象之后,就可以對(duì)其進(jìn)行操作。
大多數(shù)的 Exif 屬性,只需要視情況使用 getAttributeInt() 、getAttributeDouble()、getAttribute()(適用于 String)。它們分別表示不同類型的屬性。這些方法接收一個(gè) String 類型的參數(shù),這些參數(shù)都以常量的形式,以 TAG_Xxx 為開頭,被標(biāo)記在 ExifInterface 中。
具體想知道不同的 TAG_Xxx 需要使用什么方法獲取,可以直接看文檔。
其中注釋就已經(jīng)標(biāo)記了該屬性代表的類型。
下面舉個(gè)最常見的例子,獲取圖片的拍攝方向,用于在顯示的時(shí)候進(jìn)行旋轉(zhuǎn)。
當(dāng)然,還有一些其它比較重要的信息,例如謠傳微信原圖暴露的位置信息,可以通過 getLatLong() 方法獲取到一個(gè) float 的數(shù)組,分別表示經(jīng)度和維度,getAltitude() 獲取拍攝的海拔高度,單位是 米 。還有一些圖片,如果自帶縮略圖,可以使用 getThumbnail() 方法獲取到。更多操作,詳見代碼文檔,這里就不一一舉例了。
需要注意的是,Exif 是一個(gè)不嚴(yán)謹(jǐn)?shù)臄?shù)據(jù),它不存在任何必須的標(biāo)記字段,每個(gè)標(biāo)記字段值,都是可選的,所以我們?cè)谧x取的時(shí)候,一定要考慮讀取時(shí)的異常處理。
3.4 寫入 Exif 信息
ExifInterface 其實(shí)是不可信的,它只能作為一個(gè)參考。因?yàn)槿魏纬绦蚨伎梢詫?duì)它進(jìn)行修改。
修改 Exif 信息可以使用 setAttribute() 方法,它接收一個(gè) key-value 的鍵值對(duì)。用于標(biāo)記待修改的 Tag 和最終修改后的值。在修改完成之后,還需調(diào)用saveAttributes() 方法,否者不會(huì)將設(shè)置的 Exif 信息寫入到圖片文件中。
還有一點(diǎn)需要注意,雖然文檔中表明,Exif 信息是一個(gè)弱校驗(yàn)的數(shù)據(jù),但是它對(duì) TAG 的值是有要求的,如果不是它本身定義的值,保存并不會(huì)報(bào)錯(cuò),但是讀取的時(shí)候,會(huì)返回 null 。
四. 小結(jié)時(shí)刻
到這里你應(yīng)該就清楚了,微信原圖泄露的只是照相機(jī) App 在拍照時(shí),對(duì)圖片寫入的固有信息,并沒有什么太多的秘密,這些信息在圖片壓縮時(shí)就會(huì)被抹去。
最后再總結(jié)一下:
圖片在拍照時(shí),會(huì)寫入 Exif 信息到圖片文件中,直接發(fā)送文件會(huì)保留此信息。
99% 的圖片壓縮,都會(huì)抹去 Exif 信息。
Android 下讀取 Exif 信息,可以使用 ExifInterface。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】