java 安全体系算法调用过程

java 安全体系主要分为

  • JCA(Java Cryptography Architectrue)java加密体系
  • JCE(Java Cryptography Extension)java加密拓展
  • JSSE (Java Secure Socket Extesion)java套接字安全拓展
  • JAAS(Java Authentication and Authentication Service)Java验证和授权API

它们并不执行各种算法,只是连接应用和实际算法实现程序的一组接口。软件开发商根据JCE接口,将各种算法实现后,打包成一个Provider,可以动态地加到Java运行环境中

Android Provider 当中默认包含的算法类型

        // JCA
        addEngine("AlgorithmParameterGenerator",        false, null);
        addEngine("AlgorithmParameters",                false, null);
        addEngine("KeyFactory",                         false, null);
        addEngine("KeyPairGenerator",                   false, null);
        addEngine("KeyStore",                           false, null);
        addEngine("MessageDigest",                      false, null);
        addEngine("SecureRandom",                       false, null);
        addEngine("Signature",                          true,  null);
        addEngine("CertificateFactory",                 false, null);
        addEngine("CertPathBuilder",                    false, null);
        addEngine("CertPathValidator",                  false, null);
        addEngine("CertStore",                          false,
                            "java.security.cert.CertStoreParameters");
        // JCE
        addEngine("Cipher",                             true,  null);
        addEngine("ExemptionMechanism",                 false, null);
        addEngine("Mac",                                true,  null);
        addEngine("KeyAgreement",                       true,  null);
        addEngine("KeyGenerator",                       false, null);
        addEngine("SecretKeyFactory",                   false, null);
        // JSSE
        addEngine("KeyManagerFactory",                  false, null);
        addEngine("SSLContext",                         false, null);
        addEngine("TrustManagerFactory",                false, null);
        // JGSS
        addEngine("GssApiMechanism",                    false, null);
        // SASL
        addEngine("SaslClientFactory",                  false, null);
        addEngine("SaslServerFactory",                  false, null);
        // POLICY
        addEngine("Policy",                             false,
                            "java.security.Policy$Parameters");
        // CONFIGURATION
        addEngine("Configuration",                      false,
                            "javax.security.auth.login.Configuration$Parameters");
        // XML DSig
        addEngine("XMLSignatureFactory",                false, null);
        addEngine("KeyInfoFactory",                     false, null);
        addEngine("TransformService",                   false, null);
        // Smart Card I/O
        addEngine("TerminalFactory",                    false,
                            "java.lang.Object");

Java 安全体系扩展性很强,除了 jdk 提供的算法实现外 可以使用第三方java拓展,常用安全供应者的有

  1. Bouncy Castle
  2. Commons Codec – Apache

如何配置添加第三方的安全供应者

方法 1 .配置文件

Mac 桌面系统配置扩展 provider ,并没有加入 Bouncy Castle

配置文件在 jdk安装目录的 jre/lib/security/java.security


1.png

Android 系统配置在 security.properties


2.png

Android 默认配置了 BouncyCastleProvider ,但是阉割版的,BouncyCastleProvider 类中有的,但是 Android 上不一定会有.
3.png
  1. 代码动态添加
Security.addProvider(new BouncyCastleProvider());

(Android 手机可能不会生效,因为 Android 已经默认添加过阉割版本的 可以先移除系统自带的再添加,不过部分手机仍然不会生效);

最稳妥的办法就是 当使用到某种算法的时候就直接传入一个 Provider 对象,就会使用到我们扩展的安全供应者的算法

Signature signature = Signature.getInstance("SM3withSM2", new BouncyCastleProvider());

Android 默认配置的 Provider 初始化过程

在 java.security.Security 类当中有一段静态代码块去加载 security.properties 文件,就会把security.properties 的配置的属性加载进来到 props 对象

   static {
        props = new Properties();
        InputStream is = null;
        InputStream propStream = Security.class.getResourceAsStream("security.properties");
         is  = new BufferedInputStream(propStream);
         props.load(is);
    }

在 java.security.Provider.Providers类中的静态代码块中

 static {
        // set providerList to empty list first in case initialization somehow
        // triggers a getInstance() call (although that should not happen)
        providerList = ProviderList.EMPTY;
        // 1. 新建一个 ProviderList 对象
        providerList = ProviderList.fromSecurityProperties(); 

        // removeInvalid is specified to try initializing all configured providers
        // and removing those that aren't instantiable. This has the side effect
        // of eagerly initializing all providers.
        final int numConfiguredProviders = providerList.size();
        // 2.初始化并移除那些无法初始化的各个 Provider 
        providerList = providerList.removeInvalid();
        if (numConfiguredProviders != providerList.size()) {
            throw new AssertionError("Unable to configure default providers");
        }
    }

fromSecurityProperties 方法中会 new ProviderList();对象

在 sun.security.jca.ProviderList
读取 props 对象保存的security.properties
中security.provider 获取类名和参数构建 ProviderConfig对象

  /**
     * Return a new ProviderList parsed from the java.security Properties.
     */
    private ProviderList() {
        List<ProviderConfig> configList = new ArrayList<>();
        for (int i = 1; true; i++) {
            String entry = Security.getProperty("security.provider." + i);
            if (entry == null) {
                break;
            }
            entry = entry.trim();
            if (entry.length() == 0) {
                System.err.println("invalid entry for " +
                                   "security.provider." + i);
                break;
            }
            int k = entry.indexOf(' ');
            ProviderConfig config;
            if (k == -1) {
                config = new ProviderConfig(entry);
            } else {
                String className = entry.substring(0, k);
                String argument = entry.substring(k + 1).trim();
                config = new ProviderConfig(className, argument);
            }

            // Get rid of duplicate providers.
            if (configList.contains(config) == false) {
                configList.add(config);
            }
        }
    }

这个就拿到了配置的 provider 的类名 构建 ProviderConfig对象 并且存储在 ProviderConfig[] configs;数组当中

    /**
     * Try to load all Providers and return the ProviderList. If one or
     * more Providers could not be loaded, a new ProviderList with those
     * entries removed is returned. Otherwise, the method returns this.
     */
    ProviderList removeInvalid() {
        int n = loadAll();
        if (n == configs.length) {
            return this;
        }
        ProviderConfig[] newConfigs = new ProviderConfig[n];
        for (int i = 0, j = 0; i < configs.length; i++) {
            ProviderConfig config = configs[i];
            if (config.isLoaded()) {
                newConfigs[j++] = config;
            }
        }
        return new ProviderList(newConfigs, true);
    }

调用 loadAll 方法

    // attempt to load all Providers not already loaded
    private int loadAll() {
        int n = 0;
        for (int i = 0; i < configs.length; i++) {
            Provider p = configs[i].getProvider();
            if (p != null) {
                n++;
            }
        }
        if (n == configs.length) {
            allLoaded = true;
        }
        return n;
    }

调用各个 ProviderConfig对象的 getProvider方法

    /**
     * Get the provider object. Loads the provider if it is not already loaded.
     */
    synchronized Provider getProvider() {
        // volatile variable load
        Provider p = provider;
        if (p != null) {
            return p;
        }
        if (shouldLoad() == false) {
            return null;
        }
        if (isLoading) {
            return null;
        }
        try {
            isLoading = true;
            tries++;
            p = doLoadProvider();
        } finally {
            isLoading = false;
        }
        provider = p;
        return p;
    }

调用 doLoadProvider 方法

    private Provider doLoadProvider() {
         return initProvider(className, Object.class.getClassLoader());
    }

调用 initProvider 通过反射调用构造函数获取 Provider 对象

    private Provider initProvider(String className, ClassLoader cl) throws Exception {
        Class<?> provClass;
        if (cl != null) {
            provClass = cl.loadClass(className);
        } else {
            provClass = Class.forName(className);
        }
        Object obj;
        if (hasArgument() == false) {
            obj = provClass.newInstance();
        } else {
            Constructor<?> cons = provClass.getConstructor(CL_STRING);
            obj = cons.newInstance(argument);
        }
        if (obj instanceof Provider) {
            return (Provider)obj;
        } else {
            disableLoad();
            return null;
        }
    }

然后 配置的 Provider 类就会加载进来并且创建对象.现在已将 BouncyCastleProvider 配置进来了

举例说明 MD5 算法是如何被加载进来被使用的 ,其他的也类似.

BouncyCastleProvider 的构造函数 调用父类的构造函数 PROVIDER_NAME ,这个是一个 key ,每个 Provider 通过这个参数也能够找到相应的 Provider 对象 .

public static final String PROVIDER_NAME = "BC";
  /**
     * Construct a new provider.  This should only be required when
     * using runtime registration of the provider using the
     * <code>Security.addProvider()</code> mechanism.
     */
    public BouncyCastleProvider()
    {
        super(PROVIDER_NAME, 1.56, info);
         setup();
    }

然后调用 setup方法

    /*
     * Configurable digests
     */
    private static final String DIGEST_PACKAGE = "org.bouncycastle.jcajce.provider.digest.";
    private static final String[] DIGESTS =
    {
        // BEGIN android-removed
        // "GOST3411", "Keccak", "MD2", "MD4", "MD5", "SHA1", "RIPEMD128", "RIPEMD160", "RIPEMD256", "RIPEMD320", "SHA224",
        // "SHA256", "SHA384", "SHA512", "SHA3", "Skein", "SM3", "Tiger", "Whirlpool", "Blake2b"
        // END android-removed
        // BEGIN android-added
        "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512",
        // END android-added
    };
    
    private void setup()
    {
        loadAlgorithms(DIGEST_PACKAGE, DIGESTS);
    }

调用 loadAlgorithms ,通过 for 循环加载反射拿到相应的类对象 如 MD5算法
就会加载 org.bouncycastle.jcajce.provider.digest.MD5$Mappings 这个类(org.bouncycastle.jcajce.provider.digest.MD5的静态内部类)
并且创建对象调用 configure方法

    private void loadAlgorithms(String packageName, String[] names)
    {
        for (int i = 0; i != names.length; i++)
        {
            Class clazz = null;
            try
            {
                ClassLoader loader = this.getClass().getClassLoader();

                if (loader != null)
                {
                    clazz = loader.loadClass(packageName + names[i] + "$Mappings");
                }
                else
                {
                    clazz = Class.forName(packageName + names[i] + "$Mappings");
                }
            }
            catch (ClassNotFoundException e)
            {
                // ignore
            }

            if (clazz != null)
            {
                try
                {
                    ((AlgorithmProvider)clazz.newInstance()).configure(this);
                }
                catch (Exception e)
                {   // this should never ever happen!!
                    throw new InternalError("cannot create instance of "
                        + packageName + names[i] + "$Mappings : " + e);
                }
            }
        }
    }

在 org.bouncycastle.jcajce.provider.digest.MD5类中

package org.bouncycastle.jcajce.provider.digest;

public class MD5
{
    private MD5()
    {
    }
    /**
     * MD5 HashMac
     */
    public static class HashMac
        extends BaseMac
    {
        public HashMac()
        {
            super(new HMac(new MD5Digest()));
        }
    }

    public static class KeyGenerator
        extends BaseKeyGenerator
    {
        public KeyGenerator()
        {
            super("HMACMD5", 128, new CipherKeyGenerator());
        }
    }

    static public class Digest
        extends BCMessageDigest
        implements Cloneable
    {
        public Digest()
        {
            super(new MD5Digest());
        }

        public Object clone()
            throws CloneNotSupportedException
        {
            Digest d = (Digest)super.clone();
            d.digest = new MD5Digest((MD5Digest)digest);

            return d;
        }
    }

    public static class Mappings
        extends DigestAlgorithmProvider
    {
        private static final String PREFIX = MD5.class.getName();

        public Mappings()
        {
        }

        public void configure(ConfigurableProvider provider)
        {
            provider.addAlgorithm("MessageDigest.MD5", PREFIX + "$Digest");
            provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");

            addHMACAlgorithm(provider, "MD5", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
            addHMACAlias(provider, "MD5", IANAObjectIdentifiers.hmacMD5);
        }
    }
}

configure 方法调用 BouncyCastleProvider的 addAlgorithm

provider.addAlgorithm("MessageDigest.MD5", PREFIX + "$Digest");
provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");

PKCSObjectIdentifiers.md5 == "1.2.840.113549.2.5"

BouncyCastleProvider的 addAlgorithm调用了 put方法 ,put 是父类 Provider的方法

    public void addAlgorithm(String key, String value)
    {
        if (containsKey(key))
        {
            throw new IllegalStateException("duplicate provider key (" + key + ") found");
        }

        put(key, value);
    }

Provider的put 方法调用 implPut

    @Override
    public synchronized Object put(Object key, Object value) {
        check("putProviderProperty."+name);
        return implPut(key, value);
    }

implPut 就会把
字符串 key "MessageDigest.MD5",和 value 类名 org.bouncycastle.jcajce.provider.digest.MD5$Digest

key "Alg.Alias.MessageDigest.1.2.840.113549.2.5" 和 value "MD5"

存入legacyStrings Map 当中

    private transient Map<String,String> legacyStrings;
    
    private Object implPut(Object key, Object value) {
        if ((key instanceof String) && (value instanceof String)) {
            if (!checkLegacy(key)) {
                return null;
            }
            legacyStrings.put((String)key, (String)value);
        }
        return super.put(key, value);
    }

如何使用 BouncyCastleProvider 的 MD5 方法

MessageDigest md = MessageDigest.getInstance("MD5",new BouncyCastleProvider());

MessageDigest .getInstance 参数分别为 ("MD5",BouncyCastleProvider对象)

    public static MessageDigest getInstance(String algorithm,
                                            Provider provider)
        throws NoSuchAlgorithmException
    {
        if (provider == null)
            throw new IllegalArgumentException("missing provider");
        Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
        if (objs[0] instanceof MessageDigest) {
            MessageDigest md = (MessageDigest)objs[0];
            md.provider = (Provider)objs[1];
            return md;
        } else {
            MessageDigest delegate =
                new Delegate((MessageDigestSpi)objs[0], algorithm);
            delegate.provider = (Provider)objs[1];
            return delegate;
        }
    }

调用 Security 的 getImpl ("MD5","MessageDigest",BouncyCastleProvider对象)

    static Object[] getImpl(String algorithm, String type, Provider provider)
            throws NoSuchAlgorithmException {
        return GetInstance.getInstance
            (type, getSpiClass(type), algorithm, provider).toArray();
    }

调用GetInstance类的 getSpiClass 方法

加载 java.security.MessageDigestSpi 类并把它加入到 spiMap 缓存起来方便下次使用

    private static Class<?> getSpiClass(String type) {
        Class<?> clazz = spiMap.get(type);
        if (clazz != null) {
            return clazz;
        }
        try {
            clazz = Class.forName("java.security." + type + "Spi");
            spiMap.put(type, clazz);
            return clazz;
        } catch (ClassNotFoundException e) {
            throw new AssertionError("Spi class not found", e);
        }
    }

GetInstance类 getInstance ("MessageDigest",MessageDigestSpi.class,"MD5",BouncyCastleProvider对象)

    public static Instance getInstance(String type, Class<?> clazz,
            String algorithm, Provider provider)
            throws NoSuchAlgorithmException {
        return getInstance(getService(type, algorithm, provider), clazz);
    }

GetInstance类 getService ("MessageDigest","MD5",BouncyCastleProvider对象)

    public static Service getService(String type, String algorithm,
            Provider provider) throws NoSuchAlgorithmException {
        Service s = provider.getService(type, algorithm);
        return s;
    }

调用 BouncyCastleProvider 父类的Provider getService ("MessageDigest","MD5")

    public synchronized Service getService(String type, String algorithm) {

        ServiceKey key = previousKey;
        if (key.matches(type, algorithm) == false) {
            key = new ServiceKey(type, algorithm, false);
            previousKey = key;
        }
        ensureLegacyParsed();
        return (legacyMap != null) ? legacyMap.get(key) : null;
    }

调用 Provider 的ensureLegacyParsed 方法

在之前分析 BouncyCastleProvider 对象在初始化的过程中已经把字符串
key "MessageDigest.MD5",和 value 类名 org.bouncycastle.jcajce.provider.digest.MD5$Digest
key "Alg.Alias.MessageDigest.1.2.840.113549.2.5" 和 value "MD5"
存入legacyStrings 的Map 当中 ,在 ensureLegacyParsed方法 for 循环取出调用 parseLegacyPut

    private void ensureLegacyParsed() {
        for (Map.Entry<String,String> entry : legacyStrings.entrySet()) {
            parseLegacyPut(entry.getKey(), entry.getValue());
        }
        removeInvalidServices(legacyMap);
        legacyChanged = false;
    }

调用 Provider 的 parseLegacyPut ("MessageDigest.MD5","org.bouncycastle.jcajce.provider.digest.MD5$Digest");方法 解析 生成key 为 ServiceKey对象,Value 为 Provider.Service,放入 legacyMap当中

ServiceKey
    .type ="MessageDigest"
    .originalAlgorithm = "MD5"
    .algorithm = "MD5"

Provider.Service 
    .provider 属性为BouncyCastleProvider对象
    .type = "MessageDigest";
    .algorithm = "MD5"
    .className = "org.bouncycastle.jcajce.provider.digest.MD5$Digest"

并为 Provider.Service 添加 aliases; 1.2.840.113549.2.5,

    private transient Map<ServiceKey,Service> legacyMap;
    
    private final static String ALIAS_PREFIX = "Alg.Alias.";
    private final static String ALIAS_PREFIX_LOWER = "alg.alias.";
    private final static int ALIAS_LENGTH = ALIAS_PREFIX.length();

    private void parseLegacyPut(String name, String value) {
        if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
            // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
            // aliasKey ~ MessageDigest.SHA
            String stdAlg = value;
            String aliasKey = name.substring(ALIAS_LENGTH);
            String[] typeAndAlg = getTypeAndAlgorithm(aliasKey);
            if (typeAndAlg == null) {
                return;
            }
            String type = getEngineName(typeAndAlg[0]);
            String aliasAlg = typeAndAlg[1].intern();
            ServiceKey key = new ServiceKey(type, stdAlg, true);
            Service s = legacyMap.get(key);
            if (s == null) {
                s = new Service(this);
                s.type = type;
                s.algorithm = stdAlg;
                legacyMap.put(key, s);
            }
            legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
            s.addAlias(aliasAlg);
        } else {
            String[] typeAndAlg = getTypeAndAlgorithm(name);
            if (typeAndAlg == null) {
                return;
            }
            int i = typeAndAlg[1].indexOf(' ');
            if (i == -1) {
                // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
                String type = getEngineName(typeAndAlg[0]);
                String stdAlg = typeAndAlg[1].intern();
                String className = value;
                ServiceKey key = new ServiceKey(type, stdAlg, true);
                Service s = legacyMap.get(key);
                if (s == null) {
                    s = new Service(this);
                    s.type = type;
                    s.algorithm = stdAlg;
                    legacyMap.put(key, s);
                }
                s.className = className;
            } else { // attribute
                // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
                String attributeValue = value;
                String type = getEngineName(typeAndAlg[0]);
                String attributeString = typeAndAlg[1];
                String stdAlg = attributeString.substring(0, i).intern();
                String attributeName = attributeString.substring(i + 1);
                // kill additional spaces
                while (attributeName.startsWith(" ")) {
                    attributeName = attributeName.substring(1);
                }
                attributeName = attributeName.intern();
                ServiceKey key = new ServiceKey(type, stdAlg, true);
                Service s = legacyMap.get(key);
                if (s == null) {
                    s = new Service(this);
                    s.type = type;
                    s.algorithm = stdAlg;
                    legacyMap.put(key, s);
                }
                s.addAttribute(attributeName, attributeValue);
            }
        }
    }

GetInstance类 getInstance (Provider.Service 对象,MessageDigestSpi.class)

    public static Instance getInstance(Service s, Class<?> clazz)
            throws NoSuchAlgorithmException {
        Object instance = s.newInstance(null);
        checkSuperClass(s, instance.getClass(), clazz);
        return new Instance(s.getProvider(), instance);
    }

调用 Provider.Service newInstance方法,会加载 org.bouncycastle.jcajce.provider.digest.MD5$Digest 类并反射生成对象赋值给 instance

Provider.Service 
    .provider 属性为BouncyCastleProvider对象
    .type = "MessageDigest";
    .algorithm = "MD5"
    .className = "org.bouncycastle.jcajce.provider.digest.MD5$Digest"

然后调用 GetInstance 静态内部类GetInstance.Instance 的构造函数(BouncyCastleProvider对象,instance)

    /**
     * Static inner class representing a newly created instance.
     */
    public static final class Instance {
        // public final fields, access directly without accessors
        public final Provider provider;
        public final Object impl;
        private Instance(Provider provider, Object impl) {
            this.provider = provider;
            this.impl = impl;
        }
        // Return Provider and implementation as an array as used in the
        // old Security.getImpl() methods.
        public Object[] toArray() {
            return new Object[] {impl, provider};
        }
    }

返回的 toArray 返回一个数组
最后
MessageDigest md = MessageDigest.getInstance("MD5",new BouncyCastleProvider());
中返回的就是 MessageDigest md = (MessageDigest)objs[0];
objs[0]就是 org.bouncycastle.jcajce.provider.digest.MD5$Digest 对象.以后使用这个对象执行 MD5 的算法

这样我们就通过添加 Provider 扩展了 Java 体系的算法

经过以上的分析,我们就可以自己自定义 Provider 扩展的Java 的安全体系的算法

我们并不真的去实现 MD5 算法,只是测试我们的 Provider 是否添加成功,自己写的MD5算法是否被调用

自定义 Provider 扩展的Java 的安全体系的算法

  1. 创建一个类继承 Provider ,调用父类的构造函数
public class MyProvider extends Provider {
    /**
     * Constructs a provider with the specified name, version number,
     * and information.
     *
     * @param name    the provider name.
     * @param version the provider version number.
     * @param info    a description of the provider and its services.
     */
    protected MyProvider(String name, double version, String info) {
        super(name, version, info);
    }

    public static final String PROVIDER_NAME = "com.example.MyProvider";

    MyProvider(){
        this(PROVIDER_NAME,1.4,"com.example.MyProvider info_version v1.4");
        init();
    }

    private void init() {
        put("MessageDigest.MD5","com.example.MyMessageDigest5");
    }
}
  1. 创建 com.example.MyMessageDigest5 类 这个类必须是 MessageDigestSpi的实现类

MessageDigest 继承了 MessageDigestSpi ,最后 engineDigest()方法就是 md5 的计算结果

public class MyMessageDigest5 extends MessageDigest {

    public MyMessageDigest5() {
        super("MD5");
    }

    protected void engineUpdate(byte input) {

    }

    protected void engineUpdate(byte[] input, int offset, int len) {

    }

    protected byte[] engineDigest() {
        return "MyMessageDigest5 hello".getBytes();
    }

    protected void engineReset() {

    }
}
  1. 使用 MyProvider ,有两种使用方式一种是直接传入Provider对象,一种是用 Security.addProvider 接口添加再用 Provider的名字访问
    private static String src = "hello provider";

    public static void main(String argv[]) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5", new MyProvider());
            byte[] md5Bytes = md.digest(src.getBytes());
            System.out.println(new String(md5Bytes));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        
        try {

            Security.addProvider(new MyProvider());
            MessageDigest md = MessageDigest.getInstance("MD5", MyProvider.PROVIDER_NAME);
            byte[] md5Bytes = md.digest(src.getBytes());
            System.out.println(new String(md5Bytes));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        }

    }

最后的输出结果,证明我们的 Provider 已添加,MD5算法 已被调用

ProviderTest.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容