React Native 打包簽名方案

React Native 打包簽名方案

@Allen fengjun.dev@gmail.com

我們都知道,RN開發(fā)完成后,不管是集成到app中一起發(fā)布還是熱更新發(fā)布,都會(huì)涉及到j(luò)s代碼的打包。如果代碼中引用了靜態(tài)圖片資源,還需要連同圖片一起打包。除此以外,還要想辦法保證我們所執(zhí)行的js代碼的安全性和完整性,基于這些需求,我們形成了一套R(shí)N打包簽名的方案,本文主要對(duì)這套方案記錄,以供參考。

靜態(tài)圖片加載方式的選擇

根據(jù)官方文檔,加載靜態(tài)圖片資源一共有兩種方式。第一種是將圖片放置在原生app的res/drawable目錄中,然后在js代碼中使用下面方式加載:

<Image source={{uri: 'app_icon'}} style={{width: 40, height: 40}} />

但是這樣的方式并不優(yōu)雅,不僅會(huì)增大宿主APP的體積,還會(huì)導(dǎo)致js層的實(shí)現(xiàn)與原生層耦合在一起,當(dāng)熱更新時(shí)無法做到圖片的更新。

第二種方式是采用相對(duì)路徑的方式進(jìn)行加載,例如,我們有以下的目錄結(jié)構(gòu)

src
├── button.js
└── img
    ├── check.png
    └── check_pressed.png

然后在button.js這樣引用:

<Image source={require('./img/check.png')} />

這樣的方式就比較靈活,只要將js代碼和使用到的圖片打包在一起,就可以在熱更新的時(shí)候做到業(yè)務(wù)代碼和圖片的同步更新。

所以我們在項(xiàng)目中統(tǒng)一采用的第二種方案去加載靜態(tài)圖片。

對(duì)圖片路徑的定制

我們希望打包時(shí),整個(gè)RN包內(nèi)部是下面的結(jié)構(gòu),將所有圖片放置在images目錄下,這樣的目錄結(jié)構(gòu)比較清晰明了

├── images
│   ├── drawable-hdpi
│   ├── drawable-mdpi
│   ├── drawable-xhdpi
│   ├── drawable-xxhdpi
│   └── drawable-xxxhdpi
└── index.android.bundle

但是這里有個(gè)問題,release版本中,RN采用require方式加載圖片時(shí),生成的路徑是下面的形式:

<bundle當(dāng)前路徑>/drawable-mdpi/foldername_imagename.png

根據(jù)上面打包的結(jié)構(gòu),需要定制成為以下的形式才能正確訪問到圖片:

<bundle當(dāng)前路徑>/images/drawable-mdpi/foldername_imagename.png

所以就涉及到了對(duì)圖片路徑定制的需求,經(jīng)過RN源碼的探索,發(fā)現(xiàn)對(duì)于圖片路徑的解析是由node_modules/react-native/Libraries/Image/resolveAssetSource.js來完成的,解析生成圖片路徑主要代碼如下:

/**
 * `source` is either a number (opaque type returned by require('./foo.png'))
 * or an `ImageSource` like { uri: '<http location || file path>' }
 */
function resolveAssetSource(source: any): ?ResolvedAssetSource {
  if (typeof source === 'object') {
    return source;
  }

  var asset = AssetRegistry.getAssetByID(source);
  if (!asset) {
    return null;
  }

  const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
  if (_customSourceTransformer) {
    return _customSourceTransformer(resolver);
  }
  return resolver.defaultAsset();
}

function setCustomSourceTransformer(
  transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource,
): void {
  _customSourceTransformer = transformer;
}

可以看到,我們可以通過調(diào)用setCustomSourceTransformer來設(shè)置自己的AssetSourceResolver來完成自定義的路徑解析生成器,加上需要的images這一部分路徑即可。

Bundle打包和圖片導(dǎo)出

主要是使用官方提供的react-native bundle命令實(shí)現(xiàn)這個(gè)過程,如

react-native bundle --entry-file index.android.js --bundle-output ./output/my.bundle --dev false --platform android --assets-dest ./output/images/

具體的參數(shù)可以通過--help查看說明,需要注意的是,assets-dest指定的就是js代碼中引用到的圖片資源的導(dǎo)出路徑,如果不指定,將不會(huì)導(dǎo)出圖片。

命令執(zhí)行完成后,output目錄下:

├── images
│   ├── drawable-hdpi
│   ├── drawable-mdpi
│   ├── drawable-xhdpi
│   ├── drawable-xxhdpi
│   └── drawable-xxxhdpi
└── index.android.bundle

簽名與打包

在完成了上面的操作后,我們只是得到了一個(gè)文件夾,里面包含了bundle文件和圖片文件,如果涉及到中間人攻擊,bundle文件可能會(huì)被篡改,安全無法得到保證,所以我們需要對(duì)這個(gè)文件夾下的文件進(jìn)行加簽處理。具體采用的是下面的簽名方案:

React Native 打包簽名方案

(1)生成MANIFEST.MF文件:這是摘要文件。遍歷build目錄所有文件(entry),對(duì)非文件夾、非簽名文件的文件,逐個(gè)使用SHA1生成摘要信息,再用Base64編碼。并在摘要文件頭部寫入bundle的版本信息等。

說明:如果有人改變了安裝包中的文件,那么在安裝校驗(yàn)的時(shí)候,改變后的文件摘要信息與MANIFEST.MF的校驗(yàn)信息不同,將無法安裝。但是,如果攻擊者重新生成了摘要文件,就可以通過驗(yàn)證,所以這只是一個(gè)非常簡單的驗(yàn)證。需要結(jié)合(2)確保安全性。

(2)生成CERT.SF文件:這是摘要文件的簽名文件。對(duì)前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用私鑰進(jìn)行簽名。在安裝時(shí),在客戶端使用公鑰進(jìn)行解密,解密之后MANIFEST.MF的內(nèi)容進(jìn)行比對(duì),如果相符,則表明內(nèi)容沒有被異常修改。

說明:在這一步,即使攻擊者修改了內(nèi)容,并生成了新的摘要文件,但是攻擊者沒有開發(fā)者的私鑰,所以不能生成正確的簽名文件(CERT.SF)??蛻舳嗽趯?duì)安裝包進(jìn)行驗(yàn)證的時(shí)候,用公鑰對(duì)不正確的簽名文件進(jìn)行解密,得到的結(jié)果和摘要文件(MANIFEST.MF)對(duì)應(yīng)不起來,所以不能通過檢驗(yàn),不能成功安裝文件。從而確保了安全性。

完成上述流程后,將簽名文件和bundle等文件一起壓縮,就完成了整個(gè)打包的流程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容