web-dev-qa-db-ja.com

Javaの公開鍵でファイルを暗号化する標準的な方法は何ですか?

Javaでクライアントアプリを作成して、暗号化されたデータをサーバーに送り返します。すべてのクライアントが私のRSA公開鍵を持っています。指定されたファイルを暗号化して送信する標準的な方法はありますか?サーバーに戻る?

this one などのWebサイトから、通常、実際のファイルを暗号化するために対称キーが生成され、このキーがRSA公開キーで暗号化されてファイルに沿って送信されることがわかります。暗号化された対称鍵と対称的に暗号化されたファイルをパッケージ化するためのJavaまたは標準ファイル形式(追加のlibなし))の標準的な方法はありますか?

もちろん、独自のファイル形式を設計することもできますが、可能であれば標準に準拠したいと思います。

15

一般的な標準暗号化形式の1つは、暗号メッセージ構文(CMS、PKCS#7から発展したもの)です。 BouncyCastleはそれをサポートしています。また、署名も可能であり、S/MIMEメールメッセージのセキュリティなどの基盤となります。やり過ぎかもしれませんが、広くサポートされています。

サンプルコードは、Bouncy Castleによって提供されます。 CMSについては、おそらくエンベロープデータの例である org.bouncycastle.cms.test を参照してください。

別のオプションは、W3Cによって標準化されたXML暗号化形式です。 Javaの例については、例 XML暗号化の探索、パート1)を参照 またはXSS4Jを使用して実行: JavaでのXML暗号化の実装

8
nealmcb

ファイル暗号化の最も単純で最も確実な方法は、シェルを実行してgpgまたはpgpを呼び出して暗号化することです。洗練されていないように聞こえるかもしれませんが、多くの暗号コードを確認した私の経験では、これは暗号について多くの知識を必要とせずに、ソリューションが安全であることを保証する最も簡単な方法です。自分でやりたい場合は、暗号化のしくみについてさらに学ぶ必要があり、セキュリティ問題のリスクが高まります。

暗号化されたデータの信頼性保護があることを確認してください(暗号化してから署名/ MACなど)。機密性と信頼性の両方の保護が必要になります。非常に一般的なエラーは、暗号化するが署名に失敗することです。このエラーは、微妙なセキュリティ問題を引き起こす可能性があります。 (あなたが暗号学者でない場合、私はこれがあなたにとって意味をなさないかもしれないことを理解しています。これは決して明白ではないことを理解していますが、私は暗号学者です。IND-CCA2とINT-CTXTに精通している場合は、なぜ私がこれをお勧めするのか。これらの概念に慣れていない場合は、必要に応じてそれらを読んだり、私のWordを使ったりすることができます。暗号化はトリッキーなものです。リンク先のWebサイトがこの間違いを犯します。安全な暗号化についてのアドバイスは、そのWebサイトに依存すべきではありません。あなたの質問であなたが言及しているストローマンのアプローチもこの欠陥に悩まされており、ここで提案されている回答のいくつかにも同様の欠点があるようです。

クライアントとサーバーがリアルタイムで接続されている場合に非常に優れた代替アプローチは、クライアントとサーバー間でTLS暗号化チャネルを開き、そのチャネルを介してデータを送信することです。クライアントがサーバーの公開鍵/証明書を確認していることを確認してください。クライアントを認証する場合は、クライアントにクライアント証明書を提供し、サーバーがクライアントにクライアント証明書の提供を要求し、その証明書を検証することを確認します。

3
D.W.

@JPP、あなたのこのコメントは、あなたが間違った質問をしていると思います:

クライアントアプリは、ユーザーが保存されたファイルへの読み取り/書き込みアクセス権を持っている場合でも、コンテンツを変更せずにサーバーに公開する必要があるユーザーアクティビティログを保存します。さらに、ユーザーは内容を検査できないようにする必要があります(ファイルは、クイズアプリケーションからの運動、進行状況、およびエラーレポートです)。

クライアントアプリがログファイルを書き込んで暗号化する場合、つまり、クライアントアプリが暗号化キーにアクセスできることを意味します。
ただし、クライアントアプリのユーザーがファイルを読み取れないようにしようとしています!
次の理由により、それはうまくいきません:

  1. ユーザーは、クライアントアプリがアクセスできるすべてのもの(ファイルの内容や暗号化キーなど)にアクセスできます。
  2. ユーザーは自分のプロセスで実行されているすべてのものを制御できます。つまり、クライアントアプリにデバッガーを接続することもできます。そのため、スマートになって暗号化キーを難読化する方法を考え出さないでください。

私にとって、あなたの問題はアクセス制御のように聞こえます。ユーザーがコンテンツにアクセスできない状態で、クライアントアプリからログファイルを書き込むにはどうすればよいですか。
1つの方法は、異なるユーザーコンテキストで実行されるローカルサービス(Windows上のWindowsサービスなど)を構築することです。クライアントアプリはサービスと通信し、サービスは通常のユーザーがアクセスできない保護された場所にあるファイルにコンテンツを書き込みます。
別の代替案は、@ D.W。提案し、すぐにサーバーに直接送信し、コンテンツのファイルへの書き込みは行わないでください。

これらの両方のソリューションの問題は、前述のとおりです。ユーザーはプロセス内のすべてを制御でき、サーバーまたはローカルWindowsサービスに送信された要求をブロックまたは変更できます。

3
AviD

どうやら、たとえばBouncy Castleですでに利用可能なものを大幅に再コーディングしない限り、標準ライブラリでそれを行う方法はありません。

これが私が思いついたものです。クライアント側では、ファイルplainfileを次のように暗号化します。

// install security provider
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
    Security.addProvider(new BouncyCastleProvider());
}

// load public certificate for signing
KeyStore publicKs = KeyStore.getInstance(KeyStore.getDefaultType());
publicKs.load(new FileInputStream("/path/to/publicKeystore.ks"), null);
TrustedCertificateEntry entry = (TrustedCertificateEntry) publicKs.getEntry("public", null);
X509Certificate cert = (X509Certificate) entry.getTrustedCertificate();

// create CMS envelope data;
// check http://www.ietf.org/rfc/rfc3852.txt pages 15-16 for details
CMSEnvelopedDataGenerator envelopedDataGen = new CMSEnvelopedDataGenerator();

// specify that generated symmetric key will be encrypted by with this public key
envelopedDataGen.addKeyTransRecipient(cert);

// automatically generate the AES key, encrypt the data, and encrypt the key
CMSEnvelopedData data = envelopedDataGen.generate(new CMSProcessableFile(plainfile),
        CMSEnvelopedDataGenerator.AES256_CBC, 256, BouncyCastleProvider.PROVIDER_NAME);

byte[] encryptedData = data.getEncoded();

次に、サーバーでこのデータを次のように復号化できます。

// load private key
KeyStore privateKs = KeyStore.getInstance(KeyStore.getDefaultType());
privateKs.load(new FileInputStream("/path/to/privateKeystore.ks"), null);
PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) privateKs.getEntry("privatekey",
        new KeyStore.PasswordProtection("password".toCharArray()));
PrivateKey privateKey = privateKeyEntry.getPrivateKey();

byte[] encryptedData = ...

// parse CMS envelope data
CMSEnvelopedDataParser envelopedDataParser = new CMSEnvelopedDataParser(new ByteArrayInputStream(encryptedData));

// expect exactly one recipient
Collection<?> recipients = envelopedDataParser.getRecipientInfos().getRecipients();
if (recipients.size() != 1)
    throw new IllegalArgumentException();

// retrieve recipient and decode it
RecipientInformation recipient = (RecipientInformation) recipients.iterator().next();
byte[] decryptedData = recipient.getContent(privateKey, BouncyCastleProvider.PROVIDER_NAME);

重要な情報が欠けている場合、またはここで間違いを犯している場合は、コメントを残してください。ありがとう!