【问题标题】:How to mask sensitive values in JSON for logging purposes如何屏蔽 JSON 中的敏感值以进行日志记录
【发布时间】:2016-06-14 20:15:25
【问题描述】:

我有几个类似的 JSON 结构,我想将它们写入 SQL 表以进行日志记录。但是,JSON 中的某些字段包含敏感信息,我想对其进行部分屏蔽,因此完整值在日志中不可见。

以下是其中一种 JSON 结构的示例:

{
  "Vault": 1,
  "Transaction": {
    "gateway": {
      "Login": "Nick",
      "Password": "Password"
    },
    "credit_card": {
      "number": "4111111111111"
    }
  }
}

在这种情况下,我正在尝试更改 4111 信用卡号,使其在 JSON 中看起来像 4xxx1111。我正在使用 Newtonsoft 并将 JSON 反序列化为 JObject,但我被困在如何屏蔽该值上。我认为线索是JToken,但还没有弄清楚。我想让解决方案尽可能通用,以便它适用于我可能需要注销的任何 JSON 结构。

任何帮助将不胜感激。

【问题讨论】:

  • 反序列化过程中是否必须进行屏蔽?为什么不立即屏蔽它?
  • 根本不需要在反序列化期间完成。实际上,我已经将它反序列化为一个 JObject。我认为线索与 JToken 有关,但尚未弄清楚。这里的意图是我将获取整个包并最终将其记录到 SQL 表中。但是,我不能只输入整个信用卡号。这是一个 CYA “不,这是你发给我的”类型的日志。此外,这只是其中一种结构的示例,其他结构非常相似但不完全相同,所以我尽量保持通用。

标签: json json.net


【解决方案1】:

这是我认为我会采取的方法:

  1. 创建一个辅助方法,该方法可以获取字符串值并以日志所需的方式隐藏它。可能是这样的,例如:

    public static string Obscure(string s)
    {
        if (string.IsNullOrEmpty(s)) return s;
        int len = s.Length;
        int leftLen = len > 4 ? 1 : 0;
        int rightLen = len > 6 ? Math.Min((len - 6) / 2, 4) : 0;
        return s.Substring(0, leftLen) +
               new string('*', len - leftLen - rightLen) +
               s.Substring(len - rightLen);
    }
    
  2. 创建另一个可以接受JTokenJSONPath 表达式列表的辅助方法。在此方法中,使用SelectTokens 将每个路径与令牌的内容进行匹配。对于找到的每个匹配项,使用第一个辅助方法将敏感值替换为隐藏版本。

    public static void ObscureMatchingValues(JToken token, IEnumerable<string> jsonPaths)
    {
        foreach (string path in jsonPaths)
        {
            foreach (JToken match in token.SelectTokens(path))
            {
                match.Replace(new JValue(Obscure(match.ToString())));
            }
        }
    }
    
  3. 最后,编译一个 JSONPath 表达式列表,用于在您希望获得的所有 JSON 主体中隐藏这些值。从上面的示例 JSON 中,我认为您希望在 Password 出现的任何地方都隐藏它,如果它出现在 credit_card 中,则隐藏 number。表示为 JSONPath,它们分别是 $..Password$..credit_card.number。 (请记住,JSONPath 表达式在 Json.Net 中区分大小写。)将此列表放入某个配置设置中,以便您可以在需要时轻松更改它。

  4. 现在,每当您想注销某些 JSON 时,只需执行以下操作:

    JToken token = JToken.Parse(json);
    string[] jsonPaths = YourConfigSettings.GetJsonPathsToObscure();
    ObscureMatchingValues(token, jsonPaths);
    YourLogger.Log(token.ToString(Formatting.None));
    

演示小提琴:https://dotnetfiddle.net/dGPyJF

【讨论】:

【解决方案2】:

您可以使用 Json Converter 转换特定的命名属性以进行屏蔽。这是一个例子:

public class KeysJsonConverter : JsonConverter
{
private readonly Type[] _types;
private readonly string[] _pinValues= new[] { "number","Password" };

public KeysJsonConverter(params Type[] types)
{
    _types = types;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JToken t = JToken.FromObject(value);

        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            JObject o = (JObject)t;
            IList<JProperty> propertyNames = o.Properties().Where(p => _pinValues.Contains(p.Name)).ToList();

            foreach (var property in propertyNames)
            {
                string propertyValue = (string)property.Value;
                property.Value = propertyValue?.Length > 2 ? propertyValue.Substring(0, 2).PadRight(propertyValue.Length, '*') : "Invalid Value";
            }
            o.WriteTo(writer);
        }
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    throw new NotImplementedException();
}

public override bool CanRead
{
    get { return false; }
}

public override bool CanConvert(Type objectType)
{
    return _types.Any(t => t == objectType);
}  

}

然后将 Json 称为:JsonConvert.SerializeObject(Result, new KeysJsonConverter(typeof(Method)))

【讨论】:

    【解决方案3】:

    您可以使用反射来实现此目的,但首先,创建一个属性并标记您想要隐藏的属性:

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
    public class SensitiveDataAttribute: Attribute{}
    
    public class User
    {
        public string Username { get; set; }
    
        [SensitiveData]
        public string Password { get; set; }
    }
    
    public static class Obfuscator
    {
        private const string Masked = "***";
    
        public static T MaskSensitiveData <T> (T value) 
        {
            return Recursion(value, typeof(T));
        }
    
         # region Recursive reflection
    
        private static object Recursion(object inputObj, Type type) 
        {
            try {
                if (inputObj != null) 
                {
                    if (type.IsArray) 
                    {
                        //Input object is an array
                        //Iterate array elements
                        IterateArrayElements(ref inputObj);
                    } 
                    else 
                    {
                        //Input object is not an array
                        //Iterate properties
                        IteratePropertiesAndFields(ref inputObj);
                    }
    
                    return inputObj;
                }
            } 
            catch 
            {
                //Die quietly :'(
            }
    
        return null;
    }
    
    private static void IterateArrayElements(ref object inputObj) 
    {
        var elementType = inputObj ? .GetType().GetElementType();
        var elements = (IEnumerable)inputObj;
    
        foreach(var element in elements) 
        {
            Recursion(element, elementType);
        }
    }
    
    private static void IteratePropertiesAndFields(ref object inputObj) 
    {
        var type = inputObj ? .GetType();
    
        if (type == null)
            return;
    
        if (type.IsArray) 
        {
            //is an array
            IterateArrayElements(ref inputObj);
        } 
        else 
        {
            foreach(var property in type.GetProperties().Where(x => x.PropertyType.IsPublic)) 
            {
                if (Attribute.IsDefined(property, typeof(SensitiveDataAttribute))) 
                {
                    if (property.PropertyType == typeof(string) || type == typeof(string)) 
                    {
                        //we can mask only string
                        property.SetValue(inputObj, Masked);
                    } 
                    else 
                    {
                        //all properties that are not string set to null
                        property.SetValue(inputObj, null);
                    }
                } 
                else if (property.PropertyType.IsArray) 
                {
                    //Property is an array
                    Recursion(property.GetValue(inputObj), property.PropertyType);
                }
            }
            foreach(var property in type.GetRuntimeFields().Where(x => x.FieldType.IsPublic)) 
            {
                if (Attribute.IsDefined(property, typeof(SensitiveDataAttribute))) 
                {
                    if (property.FieldType == typeof(string) || type == typeof(string)) 
                    {
                        //we can mask only string
                        property.SetValue(inputObj, Masked);
                    } 
                    else 
                    {
                        //all Fields that are not string set to null
                        property.SetValue(inputObj, null);
                    }
                } 
                else if (property.FieldType.IsArray) 
                {
                    //Field is an array
                    Recursion(property.GetValue(inputObj), property.FieldType);
                }
            }
        }
    }
     # endregion
    }
    

    然后这样称呼它

    var user = new User
    {
       Username = "Joe",
       Password = "12345"
    }
    var myobj = Obfuscator.MaskSensitiveData<User>(user);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-01
      • 2013-10-04
      • 1970-01-01
      • 2018-08-15
      • 1970-01-01
      • 2020-05-01
      • 2023-03-22
      • 1970-01-01
      相关资源
      最近更新 更多