これは株式会社LabBase テックカレンダー 2023 4日目の記事です。
株式会社LabBaseの岩井です。
11月の上旬に、情報漏洩が発生した某社における「USB禁止」という通達が報道されました。その際「USBメモリでは?」といった反応が散見されましたが、USB接続できるUSBメモリ以外の様々な機器で情報を持ち出すことは可能であり、また、
このような商品を用いてUSBポートを封印する会社はあり(そういう会社に在籍していたことがあります)、「USB禁止」は間違ってないんじゃないのかな〜と思いました(先月の件で実際に禁止対象になったのはUSBメモリのようなので「USBメモリでは?」「USBメモリをUSBと略すな」が正しい反応です)。
そんな中で「イヤホンジャックでデータ転送するツールを作るようなエンジニアには高額の退職金を支払ってクビにしてあげてください」的なツイートを見かけ、面白そうだからやってみたいと思った次第です。(元ツイート再発見できてません、すみません)
倫理的には「どうやって情報漏洩を防ぐか」という話をすべき機会だとは思うのですが、人間の目で読めるものなら根性で暗記されたら絶対に網をすり抜けると思うので、情報漏洩を防ぐことは個人の遵法意識をどう育てるかという課題なのではないかなと思います。
それでは本題の「イヤホンジャックでデータ転送するツール」の話に移っていきます。
🤓
個人的なこだわりで、少しでも「本来のアドベントカレンダー」っぽさを出すために、毎回おもちゃ的に何か動くものを添付しようとしています。
やりたいこと
転送元のPCではファイルを音声データに変換して音を鳴らす。
転送先のPCではその音を録音し、音声データを解析して元ファイルを復元する。
図では音を空気で伝えていますが、両側凸のケーブルを送信側はイヤホン、受信側はマイクとして接続することで、周囲に音を発さず、また、ノイズを録音しないように送受信できるはずです。
仕様
データ送信側
- 任意のデータを音声データに変換する
- デバッグの都合もあるので音声データは一旦ファイルに保存し、送信時は他の標準的なソフトで再生する。
- データの開始・終了の合図として2048Hzの音を1秒鳴らす
- ファイルのビット列について、0は128Hz、1は256Hzの音を1秒鳴らすという形式に変換
周波数については色々試しましたが、440Hzや3000Hzなどでも特に問題なく受信側で解析できる感じでした。
データ受信側
- 任意のソフトで録音し、wav形式に変換したデータを読み込み、0,1を復元する
- 1bit分(今回は1秒)の1/4の長さについてフーリエ変換し、含まれる周波数を割り出す
- 高速フーリエ展開のクレートで上手く解析できなかったので、離散フーリエ変換で実装しています。
- 離散フーリエ変換を真面目にやると遅いので、128Hz,256Hz,2048Hzだけを対象として高速化する。
- 2048Hzが聞こえるまでと、その後また2048Hzが聞こえた後の音は無視する
- サンプリングレートの1/4ずつを解析するが、先端は別のタイミングの音が混ざる可能性があるので読み捨てる。
- フーリエ変換の結果、2048Hzが大きく出た2つ目のタイミングから、1/4を4つずつ進める
- 1bit分(今回は1秒)の1/4の長さについてフーリエ変換し、含まれる周波数を割り出す
離散フーリエ変換の実装についてはこちらを参考にさせていただきました。
読み捨てについての図
上側の目盛りのように、信号のタイミングと解析のタイミングが一致していれば、1つの信号を4分割して解析したどの結果を取っても正常な周波数が得られるが、下の目盛りのように不一致なタイミングになってしまうと、赤いところの解析結果が怪しくなってしまう。
2048Hz部分についても同様の理由で先頭のタイミングはいまいち信用できない。2048Hzの2番目を基準としてタイミングを合わせれば大丈夫だろうという目論見。
(受信側での録音開始のタイミングを、送信側の発音開始と完全に一致させることは不可能なので、音声開始の時間は0.00秒以降のランダムなタイミングになる)
結果
Hello, World!
とだけ書いたテキストファイルを音声に変換し、その音を再生して録音してwavに変換したものがこちらにあります(無駄に9Mバイトもある上、聞いてもしょうがないと思いますが、もし再生するのであれば音量に気をつけてください)。
※イヤホンジャックを使わずに録音したので近隣の生活音が若干ノイズとして入っています
これを受信側の処理に通した結果がこれだ!
完璧!
まとめ
これで我々はインターネット禁止+USB禁止の環境でも1bit/secの速度でデータを転送することができるようになりました。
ちなみに、この速度で1Mバイトのデータを変換するとなんと97日もの長さの音声になります。やばいですね。
実際には1bitの長さを1msecまで詰めてもいけると思います。その場合は1Mバイトで2.3時間まで早くなります。まる1日かけて10Mバイト程度なら転送できる計算ですね。
本気でやってしまうと怒られそうなこともあり、あまりデータの盗み出しに使えるものには仕上がっていません。
なんにせよ、業務ではWebのバックエンドしか触っていないこともあり、たまには全然別のものも書きたいなという欲でやっています。
Rustを書くようになって2年ほどになりますが、業務でRustを書く機会もそれほどなく、たまにRust作業が回ってくるとあまりにもモタモタしてしまう。今回調べながら色々触った結果、多少でもマシになったような気がします。やっぱり慣れるには書くしかないんだなと。
ただなぁ、Web系だと文字列操作が多いけど、Rustの文字列の扱いがいつまで経っても慣れない(以前Qiitaでその辺りのことを書いて、一応の理解はあります)。今回は整数型や実数型の扱いばっかりやっていたので、文字列操作についての学びが得られなかったのが勝ちきれなかったポイントですかね。