18

カスタムの EncryptedCharField があります。これは、UI とのインターフェイス時に基本的に CharField として表示されますが、DB に格納/取得する前に暗号化/復号化します。

カスタム フィールドのドキュメントには次のように記載されています。

  1. 追加__metaclass__ = models.SubfieldBase
  2. to_python をオーバーライドして、未加工のストレージからデータを目的の形式に変換します
  3. get_prep_value をオーバーライドして、データベースに保存する前に値を変換します。

したがって、これは簡単だと思います-2.値を復号化するだけで、3.それを暗号化するだけです。

大まかにdjango snippetに基づいており、ドキュメントではこのフィールドは次のようになります。

class EncryptedCharField(models.CharField):
  """Just like a char field, but encrypts the value before it enters the database, and    decrypts it when it
  retrieves it"""
  __metaclass__ = models.SubfieldBase
  def __init__(self, *args, **kwargs):
    super(EncryptedCharField, self).__init__(*args, **kwargs)
    cipher_type = kwargs.pop('cipher', 'AES')
    self.encryptor = Encryptor(cipher_type)

  def get_prep_value(self, value):
     return encrypt_if_not_encrypted(value, self.encryptor)

  def to_python(self, value):
    return decrypt_if_not_decrypted(value, self.encryptor)


def encrypt_if_not_encrypted(value, encryptor):
  if isinstance(value, EncryptedString):
    return value
  else:
    encrypted = encryptor.encrypt(value)
    return EncryptedString(encrypted)

def decrypt_if_not_decrypted(value, encryptor):
  if isinstance(value, DecryptedString):
    return value
  else:
    encrypted = encryptor.decrypt(value)
    return DecryptedString(encrypted)


class EncryptedString(str):
  pass

class DecryptedString(str):
  pass

Encryptor は次のようになります。

class Encryptor(object):
  def __init__(self, cipher_type):
    imp = __import__('Crypto.Cipher', globals(), locals(), [cipher_type], -1)
    self.cipher = getattr(imp, cipher_type).new(settings.SECRET_KEY[:32])

  def decrypt(self, value):
    #values should always be encrypted no matter what!
    #raise an error if tthings may have been tampered with
    return self.cipher.decrypt(binascii.a2b_hex(str(value))).split('\0')[0]

  def encrypt(self, value):
    if value is not None and not isinstance(value, EncryptedString):
      padding  = self.cipher.block_size - len(value) % self.cipher.block_size
      if padding and padding < self.cipher.block_size:
        value += "\0" + ''.join([random.choice(string.printable) for index in range(padding-1)])
      value = EncryptedString(binascii.b2a_hex(self.cipher.encrypt(value)))
    return value

モデルを保存するときに、既に復号化された文字列を復号化しようとすると、エラー、奇数長の文字列が発生します。デバッグ時に、to_python が 2 回呼び出されたように見えます。1 回目は暗号化された値で、2 回目は復号化された値で呼び出されますが、実際には Decrypted 型としてではなく、生の文字列としてエラーが発生します。さらに、get_prep_value が呼び出されることはありません。

私は何を間違っていますか?

これはそれほど難しいことではありません。この Django フィールド コードが非常に貧弱に書かれていて、特にカスタム フィールドに関しては、それほど拡張可能ではないと考える人はいますか? シンプルなオーバーライド可能な pre_save および post_fetch メソッドを使用すると、この問題を簡単に解決できます。

4

5 に答える 5

11

問題は、カスタム フィールドに値を割り当てるときに to_python も呼び出されることだと思います (このリンクに基づいて、検証の一部として行われる可能性があります)。したがって、問題は、次の状況で to_python 呼び出しを区別することです。

  1. データベースからの値が Django によってフィールドに割り当てられたとき (値を解読したいとき)
  2. カスタム フィールドに値を手動で割り当てる場合 (例: record.field = value)

使用できるハックの 1 つは、値の文字列にプレフィックスまたはサフィックスを追加し、isinstanceチェックを行う代わりにそれをチェックすることです。

例を書くつもりでしたが、これを見つけました(さらに良い:))。

BaseEncryptedFieldを確認してください: https://github.com/django-extensions/django-extensions/blob/2.2.9/django_extensions/db/fields/encrypted.py (フィールドが 3.0.0 で削除されたため、古いバージョンへのリンク。非推奨の理由については、 Issue #1359を参照してください)

ソース: Django カスタム フィールド: DB からの値に対してのみ to_python() を実行しますか?

于 2012-11-12T14:20:22.007 に答える
4

to_pythonスニペットのように、オーバーライドする必要があります。

クラスを見ると、メソッドCharFieldがないことがわかります。value_to_string

ドキュメントによると、このメソッドto_pythonは3つのことを処理する必要があります。

  • 正しいタイプのインスタンス
  • 文字列(たとえば、デシリアライザーから)。
  • 使用している列タイプに対してデータベースが返すものは何でも。

現在、3番目のケースのみを扱っています。

これを処理する1つの方法は、復号化された文字列用の特別なクラスを作成することです。

class DecryptedString(str):
   pass

次に、このクラスを検出して、次の場所で処理できますto_python()

def to_python(self, value):
    if isinstance(value, DecryptedString):
        return value

    decrypted = self.encrypter.decrypt(encrypted)
    return DecryptedString(decrypted)

これにより、複数回復号化することができなくなります。

于 2012-10-25T20:56:49.887 に答える
3

メタクラスを設定するのを忘れました:

class EncryptedCharField(models.CharField):
    __metaclass__ = models.SubfieldBase

カスタム フィールドのドキュメントに、これが必要な理由が説明されています。

于 2012-10-25T21:44:54.897 に答える
2

この質問が最初に回答されて以来、この正確な問題を解決するために多くのパッケージが作成されました。

たとえば、2018 年現在、パッケージdjango-encrypted-model-fieldsは次のような構文でこれを処理します。

from encrypted_model_fields.fields import EncryptedCharField

class MyModel(models.Model):
    encrypted_char_field = EncryptedCharField(max_length=100)
    ...

経験則として、より成熟したソリューションがそこに存在する場合に、独自のソリューションをセキュリティ課題に転がすことは通常悪い考えです.コミュニティはあなたよりも優れたテスターおよびメンテナーです.

于 2018-09-27T21:50:26.560 に答える
1

すでに復号化された値を渡すなど、さまざまなケースを処理する to_python メソッドを追加する必要があります

(警告: スニペットは私自身のコードから切り取られています - 説明のためだけです)

def to_python(self, value):
    if not value:
        return
    if isinstance(value, _Param): #THIS IS THE PASSING-ON CASE
        return value
    elif isinstance(value, unicode) and value.startswith('{'):
        param_dict = str2dict(value)
    else:
        try:
            param_dict = pickle.loads(str(value))
        except:
            raise TypeError('unable to process {}'.format(value))
    param_dict['par_type'] = self.par_type
    classname = '{}_{}'.format(self.par_type, param_dict['rule'])
    return getattr(get_module(self.par_type), classname)(**param_dict)

ところで:

代わりにget_db_prep_value使用する必要がありますget_prep_value(前者は db 固有の変換用です - https://docs.djangoproject.com/en/1.4/howto/custom-model-fields/#converting-python-objects-to-query-valuesを参照)

于 2012-10-25T21:50:14.160 に答える