Asp.Net Core Identity 프라이버시 데이터 보호의 실현

머리말
Asp.Net Core Identity 는 Asp.Net Core 의 중요 한 구성 부분 으로 그 는 Asp.Net Core 심지어 다른.Net Core 응용 프로그램 에 간단 하고 사용 하기 쉬 우 며 확장 하기 쉬 운 기초 사용자 관리 시스템 프레임 워 크 를 제공 했다.기본 적 인 사용자,캐릭터,제3자 로그 인,Claim 등의 기능 을 포함 하고 있 으 며,Identity Server 4 를 사용 하면 OpenId connection 과 Oauth 2.0 관련 기능 을 쉽게 확장 할 수 있다.인터넷 에 이미 대량의 관련 글 이 소개 되 었 지만 이것 은 Asp.Net Core Identity 의 전부 가 아니 라 그 중 하 나 는 바로 프라이버시 데이터 보호 이다.
본문
얼핏 보면 프라이버시 데이터 보호 가 뭔 지 알 것 같 지만 잘 모 르 겠 어 요.확실히 이 물건 은 말 만 으로 는 설명 하기 어렵다.그러면 바로 그림 을 올 려 라.

이것 은 사용자 표 의 일부분 입 니 다.문제점 을 발견 하 셨 습 니까?사용자 이름과 이메일 필드 는 알 아 볼 수 없 는 것들 로 변 했다.자세히 보면 이 문자열 은 규칙 이 있 는 것 같 습 니 다.guid+사칭+base 64 인 코딩 문자열 인 것 같 습 니 다.물론 이 문자열 은 온라인 디 코딩 을 하 는 결과 가 어 지 러 운 것 같 습 니 다.예 를 들 어 id 가 1 인 Username:svBqhhluYZSiPZVUF4baOQ=온라인 디 코딩 후²ðj†na”¢=•T†Ú9 。
이것 이 바로 프라이버시 데이터 보호 입 니 다.만약 에 이 기능 이 없 으 면 사용자 이름 은 명문 으로 저 장 됩 니 다.비밀 번 호 는 hash 로 풀 기 어렵 지만 라 이브 러 리 에 끌 리 면 사용자 데이터 도 더욱 큰 위험 에 직면 할 것 입 니 다.서로 다른 사이트 에서 같은 계 정 정 정 보 를 이용 해 등록 하 는 것 을 선 호 해 잊 혀 지지 않 기 때문이다.한 사이트 의 비밀번호 가 도 둑 맞 고 다른 사이트 가 라 이브 러 리 로 끌 리 면 해커 는 같은 사용자 이름 이 있 는 지 비교 해 라 이브 러 리 충돌 을 시도 할 수 있 고,심지어 이메일 이 도 둑 맞 으 면 해커 는 이메일 이 비밀 번 호 를 찾 아 NTR 에 계 정 을 주 는 것 도 지 켜 볼 수 있다.한편,프라이버시 데이터 보 호 는 더욱 튼튼한 뒷받침 이다.라 이브 러 리 에 끌 려 도 해커 는 안에 있 는 것 을 알 아 보지 못 한다.
그 다음 에 이 형식 은 대체적으로 생각 할 수 있 습 니 다.짝 퉁 은 구분자 이 고 앞 에 있 는 guid 이 며 뒤 에는 암호 화 된 내용 입 니 다.그 문 제 는 guid 가 되 고 또 뭐 하 는 거 야?그냥 암호 화 된 내용 을 저장 하면 되 잖 아.이것 은 마이크로소프트 개발 프레임 워 크 가 디 테 일 을 중시 하 는 가장 좋 은 표현 이 고 그 다음 에 코드 를 결합 하면 결말 을 알 수 있다.
프라이버시 데이터 보호 사용

 //  Identity  (  EF  , EF       )
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
 //...
 options.Stores.ProtectPersonalData = true; //           
})
//...
.AddPersonalDataProtection<AesProtector, AesProtectorKeyRing>(); //          ,      ,      ,      
그 중의 AesProtector 와 AesProtector KeyRing 은 스스로 실현 해 야 한다.마이크로소프트 는 기 존의 종 류 를 제공 하지 않 았 다.적어도 나 는 찾 지 못 했다.아마도 이 기능 이 인기 가 없 는 원인 일 것 이다.Neter 는 마이크로소프트 에 의 해 버릇 이 나 빠 져 서 모두 옷 으로 손 을 내밀 고 밥 으로 입 을 벌 렸 다.AesProtector KeyRing 에 KeyRing 이라는 글자 가 있 는 것 을 발 견 했 습 니까?열쇠 꾸러미,맞 힌 것 을 축하합니다.guid 가 바로 이 열쇠 꾸러미 의 열쇠 번호 입 니 다.암호 화 된 열쇠 가 도 둑 맞 았 지만 모두 도 둑 맞 은 것 이 아니라면 사용자 정보 가 모두 유출 되 지 는 않 는 다 는 것 이다.마이크로소프트 의 이 수단 은 정말 잔인 하 다!
다음은 이 두 종류 가 무엇 인지 보 자.
AesProtector 는 ILookupProtector 의 실현 이다.인 터 페 이 스 는 암호 화 와 복호화 에 사용 되 는 두 가지 방법 을 포함 하고 문자열 을 되 돌려 줍 니 다.매개 변 수 는 문자열 데이터 와 위 에 있 는 guid 를 포함 합 니 다.물론 실제 문자열 이면 됩 니 다.guid 는 제 개인 적 인 선택 입 니 다.중복 되 지 않 는 문자열 을 만 드 는 것 이 편리 합 니까?아니면 guid 를 만 드 는 것 이 편리 합 니까?
AesProtectorKeyRing 은 ILookupProtectorKeyRing 의 실현 이다.인 터 페 이 스 는 현재 사용 하고 있 는 열쇠 번호 의 읽 기 전용 속성 을 포함 하여 암호 화 열 쇠 를 제공 합 니 다.2.열쇠 번호 에 따라 문자열 의 색인 기 를 가 져 옵 니 다.3.모든 열쇠 번 호 를 얻 는 방법.
AesProtector

 class AesProtector : ILookupProtector
  {
    private readonly object _locker;

    private readonly Dictionary<string, SecurityUtil.AesProtector> _protectors;

    private readonly DirectoryInfo _dirInfo;

    public AesProtector(IWebHostEnvironment environment)
    {
      _locker = new object();

      _protectors = new Dictionary<string, SecurityUtil.AesProtector>();

      _dirInfo = new DirectoryInfo($@"{environment.ContentRootPath}\App_Data\AesDataProtectionKey");
    }

    public string Protect(string keyId, string data)
    {
      if (data.IsNullOrEmpty())
      {
        return data;
      }

      CheckOrCreateProtector(keyId);

      return _protectors[keyId].Protect(Encoding.UTF8.GetBytes(data)).ToBase64String();
    }

    public string Unprotect(string keyId, string data)
    {
      if (data.IsNullOrEmpty())
      {
        return data;
      }

      CheckOrCreateProtector(keyId);

      return Encoding.UTF8.GetString(_protectors[keyId].Unprotect(data.ToBytesFromBase64String()));
    }

    private void CheckOrCreateProtector(string keyId)
    {
      if (!_protectors.ContainsKey(keyId))
      {
        lock (_locker)
        {
          if (!_protectors.ContainsKey(keyId))
          {
            var fileInfo = _dirInfo.GetFiles().FirstOrDefault(d => d.Name == $@"key-{keyId}.xml") ??
                    throw new FileNotFoundException();
            using (var stream = fileInfo.OpenRead())
            {
              XDocument xmlDoc = XDocument.Load(stream);
              _protectors.Add(keyId,
                new SecurityUtil.AesProtector(xmlDoc.Element("key")?.Element("encryption")?.Element("masterKey")?.Value.ToBytesFromBase64String()
                  , xmlDoc.Element("key")?.Element("encryption")?.Element("iv")?.Value.ToBytesFromBase64String()
                  , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("BlockSize")?.Value)
                  , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("KeySize")?.Value)
                  , int.Parse(xmlDoc.Element("key")?.Element("encryption")?.Attribute("FeedbackSize")?.Value)
                  , Enum.Parse<PaddingMode>(xmlDoc.Element("key")?.Element("encryption")?.Attribute("Padding")?.Value)
                  , Enum.Parse<CipherMode>(xmlDoc.Element("key")?.Element("encryption")?.Attribute("Mode")?.Value)));
            }
          }
        }
      }
    }
  }
AesProtectorKeyRing

class AesProtectorKeyRing : ILookupProtectorKeyRing
  {
    private readonly object _locker;
    private readonly Dictionary<string, XDocument> _keyRings;
    private readonly DirectoryInfo _dirInfo;

    public AesProtectorKeyRing(IWebHostEnvironment environment)
    {
      _locker = new object();
      _keyRings = new Dictionary<string, XDocument>();
      _dirInfo = new DirectoryInfo($@"{environment.ContentRootPath}\App_Data\AesDataProtectionKey");

      ReadKeys(_dirInfo);
    }

    public IEnumerable<string> GetAllKeyIds()
    {
      return _keyRings.Keys;
    }

    public string CurrentKeyId => NewestActivationKey(DateTimeOffset.Now)?.Element("key")?.Attribute("id")?.Value ?? GenerateKey(_dirInfo)?.Element("key")?.Attribute("id")?.Value;

    public string this[string keyId] =>
      GetAllKeyIds().FirstOrDefault(id => id == keyId) ?? throw new KeyNotFoundException();

    private void ReadKeys(DirectoryInfo dirInfo)
    {
      foreach (var fileInfo in dirInfo.GetFiles().Where(f => f.Extension == ".xml"))
      {
        using (var stream = fileInfo.OpenRead())
        {
          XDocument xmlDoc = XDocument.Load(stream);

          _keyRings.TryAdd(xmlDoc.Element("key")?.Attribute("id")?.Value, xmlDoc);
        }
      }
    }

    private XDocument GenerateKey(DirectoryInfo dirInfo)
    {
      var now = DateTimeOffset.Now;
      if (!_keyRings.Any(item =>
        DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now
        && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now))
      {
        lock (_locker)
        {
          if (!_keyRings.Any(item =>
            DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now
            && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now))
          {
            var masterKeyId = Guid.NewGuid().ToString();

            XDocument xmlDoc = new XDocument();
            xmlDoc.Declaration = new XDeclaration("1.0", "utf-8", "yes");

            XElement key = new XElement("key");
            key.SetAttributeValue("id", masterKeyId);
            key.SetAttributeValue("version", 1);

            XElement creationDate = new XElement("creationDate");
            creationDate.SetValue(now);

            XElement activationDate = new XElement("activationDate");
            activationDate.SetValue(now);

            XElement expirationDate = new XElement("expirationDate");
            expirationDate.SetValue(now.AddDays(90));

            XElement encryption = new XElement("encryption");
            encryption.SetAttributeValue("BlockSize", 128);
            encryption.SetAttributeValue("KeySize", 256);
            encryption.SetAttributeValue("FeedbackSize", 128);
            encryption.SetAttributeValue("Padding", PaddingMode.PKCS7);
            encryption.SetAttributeValue("Mode", CipherMode.CBC);

            SecurityUtil.AesProtector protector = new SecurityUtil.AesProtector();
            XElement masterKey = new XElement("masterKey");
            masterKey.SetValue(protector.GenerateKey().ToBase64String());

            XElement iv = new XElement("iv");
            iv.SetValue(protector.GenerateIV().ToBase64String());

            xmlDoc.Add(key);
            key.Add(creationDate);
            key.Add(activationDate);
            key.Add(expirationDate);
            key.Add(encryption);
            encryption.Add(masterKey);
            encryption.Add(iv);

            xmlDoc.Save(
              $@"{dirInfo.FullName}\key-{masterKeyId}.xml");

            _keyRings.Add(masterKeyId, xmlDoc);

            return xmlDoc;
          }

          return NewestActivationKey(now);
        }
      }

      return NewestActivationKey(now);
    }

    private XDocument NewestActivationKey(DateTimeOffset now)
    {
      return _keyRings.Where(item =>
          DateTimeOffset.Parse(item.Value.Element("key")?.Element("activationDate")?.Value) <= now
          && DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value) > now)
        .OrderByDescending(item =>
          DateTimeOffset.Parse(item.Value.Element("key")?.Element("expirationDate")?.Value)).FirstOrDefault().Value;
    }
  }
이 두 종류 도 Asp.Net Core DI 에 등 록 된 서비스 로 모든 DI 기능 이 지원 된다.
그 중에서 저 는 제 가 다른 곳 에서 쓴 바 텀 기초 도구 류 도 사 용 했 습 니 다.완 성 된 실현 을 보고 싶 으 면 제 Github 복제 코드 를 실제로 실행 하고 체험 할 수 있 습 니 다.여기 서 이 두 가지 유형의 디자인 방향 을 대충 말 해 보 세 요.마이크로소프트 가 열쇠 고리 기능 을 설계 한 이상 당연히 잘 이용 해 야 한다.나 는 코드 에 모든 열쇠 의 유효기간 90 일 을 적 었 다.기한 이 지나 면 자동 으로 새로운 열 쇠 를 생 성하 고 사용 할 것 이다.열쇠 의 상세 한 정 보 는 xml 문 서 를 사용 하여 프로젝트 폴 더 에 저장 하고 아래 의 캡 처 를 구체 적 으로 볼 수 있다.Identity 는 최신 열 쇠 를 사용 하여 암호 화하 고 열쇠 번 호 를 데이터베이스 에 저장 하 며 읽 을 때 번호 에 따라 해당 하 는 암호 기 복호화 데 이 터 를 찾 습 니 다.이 과정 은 EF Core 의 값 변환기(EF Core 2.1 증가)로 이 뤄 졌 으 며,Identity 가 DbContext 에서 암호 화해 야 할 필드 에 값 변환 기 를 등록 한 것 이다.그래서 저도 초기 에 Identity 에 이런 기능 이 있 었 는 지,EF Core 를 사용 하지 않 은 상황 에서 이 기능 이 사용 가능 한 지 잘 모 르 겠 습 니 다.
사용자 정의 데 이 터 를 보호 하려 면 속성 에[PersonalData]특성 을 표시 하면 됩 니 다.Identity 는 위 에서 언급 한 UserName 과 같은 내부 의 일부 속성 을 표시 했다.
특별히 주의해 야 할 몇 가지 점 이 있다.
1.데이터 가 있 는 상황 에서 데이터 보호 기능 을 함부로 켜 거나 닫 지 마 십시오.그렇지 않 으 면 심각 한 결 과 를 초래 할 수 있 습 니 다.
2.열 쇠 는 반드시 잘 보호 하고 잘 보관 해 야 합 니 다.그렇지 않 으 면 사용자 데 이 터 를 누설 하거나 더 이상 사용자 데 이 터 를 복호화 할 수 없 을 수도 있 습 니 다.라 이브 러 리 삭제 부터 도망 가 는 시 프 트+Del 까지 절대 하지 마 세 요.
3.보 호 된 필드 는 데이터베이스 에서 모호 한 검색 을 수행 할 수 없고 정확하게 일치 할 수 있 습 니 다.데이터 분석 을 하려 면 Identity 로 데 이 터 를 메모리 에 읽 어야 다른 일 을 계속 할 수 있 습 니 다.
4.열쇠 의 유효기간 이 너무 짧 으 면 안 됩 니 다.사용자 가 로그 인 할 때 Identity 는 사용자 가 언제 등 록 했 는 지,어떤 열 쇠 를 사용 해 야 하 는 지 모 르 기 때문에 Identity 는 모든 열쇠 로 암호 화한 다음 에 정확 한 일치 기록 이 있 는 지 찾 습 니 다.열쇠 의 유효기간 이 짧 을 수록 사이트 운영 시간 이 늘 어 나 면서 열쇠 의 수가 늘 어 나 고 시도 해 야 할 열쇠 도 늘 어 나 시스템 성능 에 영향 을 미친다.물론 캐 시 로 풀 수 있다.
효과 미리 보기:


본문 주소:https://www.cnblogs.com/coredx/p/12210232.html

전체 소스 코드:Github
안에 여러 가지 작은 것들 이 있 는데,이것 은 그 중의 하나 일 뿐,싫어 하지 않 는 다 면 스타 해 볼 수 있다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기