ホームページ開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでリッチテキストを扱う・暗号化を試す

 Xojo / Real Studio Trial and Error

CocoaのDeclareでリッチテキストを扱う・暗号化を試す

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 リッチテキスト(NSAttributedString)の暗号化について試してみました。
ここで言う暗号化は、「それなりの技能を持った人物が(ファイルに保存する等した)バイナリデータをハッキングしても、直ちにその内容(文字列)を知られることがない」といった場合を想定していて、例えば、インターネット上で秘匿性の高いデータを安全にやり取りする、は想定していない。
 なお検証には、Xojo 2016 Release 1.1を用いています。(Mac mini mid 2010 + OS X 10.11.5 El Capitan)


 方針

 当初は、リッチテキストがCocoaベースのこともあって、Cocoaの暗号化機能を使おうかと思ったのですが、意外(?)にも、標準で使えるものが見当たりませんでした。
 一方、Xojoは標準で暗号化の機能(Crypto)を持っていることが分かったので、これを使ってみることにしました。

 暗号化の作法としては、テキストのみが一般的(?)な気もしますが、今回は敢えてNSAttributedString丸ごとを暗号化してみます。
リファレンスには、CryptoはStringを対象とする?かのような記述があって、例題もStringベースだが、入力がMemoryBlockであることから、Stringでなくてもいけそうな気がしたので、それも含めてのトライアルとした。
 ただし、NSAttributedStringのままではXojo側で処理できない(と思われる)ため、一度NSDataに変換してバイト列を抽出し、暗号化の入力であるMemoryBlockに1バイトずつコピーする方法を採りました。
NSDataへの変換に、まずはNSAttributedStringのRTFDFromRange:documentAttributes:メソッドを使ったところ、NSAttributedStringに再変換した際に日本語が文字化けする問題が発生。オプション設定でエンコーディングを指定してみたりもしたが解決しなかったため、次にNSKeyedArchiverを試してみたところ、文字化けしなかったので、こちらを使うこととした。(サイズが倍くらいになってしまうが、止むを得ないか?)
 また、Cryptoでは一度に暗号化可能なバイト数に制限があるため、それを超えるケース(というか、今回の遣り方では必ず超える)ではデータを分割して暗号化/保持し、利用時は復号化後に連結することにしました。
 なお、今回は分割データを別個に保持するようにしましたが、(ポジションを記録した上で)連結してしまう方法もあるかとは思います。
注)参考までに、鍵のビット数と一度に暗号化可能なバイト数の関係を実測してみたところ、以下の通りとなった。(あくまで実測なので、常にそうなるかは不明。)
鍵のビット数 一度に暗号化可能なバイト数
1024 86
2048 214
4096 470
4096では鍵の取得に数秒かかるようになり、さらに大きくしていくとアプリが「応答なし」となってしまった。
リファレンスには1024か2048使えとあるが、実用性を考えても、その辺りが妥当な線なのかも。

 Xojoでの実装
  1. 前回プロジェクトをベースとする(注:ファイルIO関連の機能とは連携していないため、使わないようにするか、コメントアウトして下さい。)
  2. TextAreaをWindow1に追加(Name:TextArea2)
  3. BevelButtonを2個Window1に追加(Name:BevelButton1、BevelButton2)
  4. 以下をWindow1のOpenイベントに追加(前回プロジェクトで記述したコードの後に追加)
    // Dictionaryを生成
    dict = new Dictionary
    
    // 鍵のビット数を2048にして、暗号化用秘密鍵と公開鍵を生成
    if not Crypto.RSAGenerateKeyPair( 2048, privateKey, publicKey ) then
        MsgBox "error."
    end if
    
  5. 以下をBevelButton1のActionイベントに追加
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextAreaの取得
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    Dim pnt1 As Ptr = documentView(TextArea1.Handle)  // TextArea1.Handle = NSScrollView*
    
    // NSTextStorageの取得>NSAttributedStringの取得
    declare function textStorage lib "Cocoa" selector "textStorage" (obj_id as Ptr) As Ptr  // Return NSTextStorage*
    Dim pnt2 As Ptr = textStorage(pnt1)
    
    // NSAttributedStringをNSDataに変換(NSKeyedArchiverを使用)
    Dim pnt4 As Ptr = NSClassFromString("NSKeyedArchiver")  // クラスメソッドなので、まずNSKeyedArchiverクラスを取得
    Declare Function archivedDataWithRootObject Lib "Cocoa" Selector "archivedDataWithRootObject:" (receiver As Ptr, obj As Ptr) As Ptr
    pnt4 = archivedDataWithRootObject(pnt4, pnt2)  // Return NSData*
    
    // NSDataからバイト列を取得
    declare function myBytes lib "Cocoa" selector "bytes" (obj_id as Ptr) As Ptr
    Dim pnt5 As Ptr = myBytes(pnt4)
    
    // バイト列の長さを取得
    declare function myLength lib "Cocoa" selector "length" (obj_id as Ptr) As Integer
    Dim length As Integer = myLength(pnt4)
    
    // 1バイトずつコピー
    Dim baseData As new MemoryBlock(length)
    for i As Integer = 0 to length-1
        baseData.Byte(i) = pnt5.Byte(i)
    next
    
    // データをlimitバイトで分割(一度に暗号化できるバイト数に制限があるため)
    Dim limit As Integer = 200  // 鍵のビット数が2048では、214バイトが上限値?
    Dim blk As Integer = Floor(length/limit)+1
    Dim stb, bln As Integer
    for j As Integer = 1 to blk
        
        // 分割バイト数のセット(最後は残りバイト数にする)
        stb = (j-1)*limit  // 0ベース
        if stb+limit < length then
          bln = limit
        else
          bln = length - stb
        end if
        
        // 暗号化
        Dim encryptedData As MemoryBlock = Crypto.RSAEncrypt(baseData.MidB(stb,bln),publicKey)
        
        // 暗号化したデータをセット
        dict.Value(j) = encryptedData
        
    next
    
    // 分割数をセット
    dict.Value(0) = blk
    
    注)1バイトずつコピーの代替法については、こちらを参照。(2019.03.26)
  6. 以下をBevelButton2のActionイベントに追加
    // 暗号化前なら警告
    if not dict.HasKey(0) then
        MsgBox "まず暗号化して下さい。"
        return
    end if
    
    // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。
    Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr
    
    // TextAreaの取得
    declare function documentView lib "Cocoa" selector "documentView" (obj_id as Integer) as Ptr  // Return NSTextView*
    Dim pnt1 As Ptr = documentView(TextArea2.Handle)  // TextArea2.Handle = NSScrollVew*
    
    // NSTextStorageの取得
    declare function textStorage lib "Cocoa" selector "textStorage" (obj_id as Ptr) As Ptr  // Return NSTextStorage*
    Dim pnt2 As Ptr = textStorage(pnt1)
    
    //分割データごとに復号化して連結
    Dim baseData As new MemoryBlock(0)
    Dim cnt As Integer = dict.Value(0)  // 分割数
    for j As Integer = 1 to cnt
    
        // 暗号化されたデータをMemoryBlockで受ける(Valueはバリアント型なので、型を確定する)
        Dim encryptedData As MemoryBlock = dict.Value(j)
        
        // 復号化
        Dim decryptedData As MemoryBlock = Crypto.RSADecrypt(encryptedData,privateKey)
        
        // MemoryBlockを連結
        baseData = baseData.Operator_Add(decryptedData)
    
    next
    
    // 復号化したデータはバイト列なので、NSData型に復元
    Declare Function alloc Lib "Cocoa" Selector "alloc" (receiver As Ptr) As Ptr
    Declare Function initWithBytes Lib "Cocoa" Selector "initWithBytes:length:" (receiver As Ptr, dat As Ptr, len As Integer) As Ptr
    Dim pnt3 As Ptr = NSClassFromString("NSData")
    pnt3 = alloc(pnt3)
    pnt3 = initWithBytes(pnt3, baseData, baseData.Size)
    
    // NSData型からNSMutableAttributedString型に復元(NSKeyedUnarchiverを使用)
    Dim attrString As Ptr = NSClassFromString("NSKeyedUnarchiver")  // クラスメソッドなので、まずNSKeyedUnarchiverクラスを取得
    Declare Function unarchiveObjectWithData Lib "Cocoa" Selector "unarchiveObjectWithData:" (receiver As Ptr, dat As Ptr) As Ptr
    attrString = unarchiveObjectWithData(attrString, pnt3)
    
    // ストレージにスタイル付きテキストをセット
    Declare Sub setAttributedString Lib "Cocoa" Selector "setAttributedString:" (receiver As Ptr, identifier As Ptr)
    setAttributedString(pnt2, attrString)
    
  7. 以下をWindow1のプロパティに追加
    プロパティ名: dict
    データ型: Dictionary
    標準値: なし
    
  8. 以下をWindow1のプロパティに追加
    プロパティ名: privateKey
    データ型: String
    標準値: なし
    
  9. 以下をWindow1のプロパティに追加
    プロパティ名: publicKey
    データ型: String
    標準値: なし
    
 実行してみたところ、暗号化/復号化が機能することを確認しました。
S Shot1


 おわりに

 Cryptoの本来の使い方とは違う?ような気もするので、データ量が多くなったりした場合等のパフォーマンス(と精度)は未知数です。
 また、ファイルに保存する場合は、オリジナルのファイルIOを改変するか、シリアライズが使える筈です。ただしこの時、privateKeyも一緒に保存しておく必要があります。(本記事の趣旨からすれば、privateKey自身の暗号化は不要と考えます。もしくは、キー(文字列)を適当に分割して並べ替える程度で十分かと。)

 なお、今回も仕組みを理解するために、極力シンプルな書き方を心懸けています。
(例えば、allocした変数の一部は然るべきタイミングでReleaseするべきと思われますが、そのためにはコードを工夫する必要があります。)


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)


 更新履歴

 2019.03.26 1バイトずつコピーの代替法へのリンクを追加
 2016.05.30 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]