java使用SM2算法生成密鑰對加密解密加簽驗簽

簡介

SM2是非對稱加密算法酝静,一提非對稱加密算法辩棒,第一想到的是RSA狼忱,沒錯,這個就是替代RSA的一睁。

它是基于橢圓曲線密碼的公鑰密碼算法標準钻弄,其秘鑰長度256bit,包含數(shù)字簽名者吁、密鑰交換和公鑰加密窘俺,用于替換RSA/DH/ECDSA/ECDH等國際算法「吹剩可以滿足電子認證服務系統(tǒng)等應用需求瘤泪,由國家密碼管理局于2010年12月17號發(fā)布。

SM2采用的是ECC 256位的一種育八,其安全強度比RSA 2048位高对途,且運算速度快于RSA。隨著密碼技術和計算技術的發(fā)展髓棋,目前常用的1024位RSA算法面臨嚴重的安全威脅掀宋,我們國家密碼管理部門經(jīng)過研究,決定采用SM2橢圓曲線算法替換RSA算法仲锄。SM2算法在安全性、性能上都具有優(yōu)勢湃鹊。

用途

可以用于前后端傳輸數(shù)據(jù)加密解密
可以用于對數(shù)據(jù)加簽驗簽儒喊,確保報文的安全性和完整性。

比如币呵,生成一套前端公私鑰密鑰對怀愧,生成一套后端服務器公私鑰密鑰對侨颈。
前端把參數(shù)json字符串通過服務器公鑰用sm2算法加密,服務器后端接收到請求后用服務器私鑰解密芯义,拿到原始參數(shù)哈垢,處理數(shù)據(jù)并生成響應數(shù)據(jù),把響應數(shù)據(jù)用前端公鑰加密扛拨,前端接收到響應加密后數(shù)據(jù)耘分,用前端私鑰解密,拿到響應json绑警。這個過程是快速且安全的求泰。

一般這個過程在網(wǎng)關上公共實現(xiàn)。

java使用

maven引入bcprov-jdk15on jar包,截止發(fā)文计盒,最新版本是1.70
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
新建StandardSM2Engine實體類
package com.zhaohy.app.utils;

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;

/**
 * 自定義SM2Engine類渴频,對加密解密數(shù)據(jù)進行了ASN.1編碼
 */
public class StandardSM2Engine {

    private final Digest digest;
    private final Mode mode;

    private boolean forEncryption;
    private ECKeyParameters ecKey;
    private ECDomainParameters ecParams;
    private int curveLength;
    private SecureRandom random;

    public StandardSM2Engine() {
        this(new SM3Digest());
    }

    public StandardSM2Engine(Mode mode) {
        this(new SM3Digest(), mode);
    }

    public StandardSM2Engine(Digest digest) {
        this(digest, Mode.C1C2C3);
    }

    public StandardSM2Engine(Digest digest, Mode mode) {
        if (mode == null) {
            throw new IllegalArgumentException("mode cannot be NULL");
        }
        this.digest = digest;
        this.mode = mode;
    }

    public void init(boolean forEncryption, CipherParameters param) {
        this.forEncryption = forEncryption;

        if (forEncryption) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;

            ecKey = (ECKeyParameters) rParam.getParameters();
            ecParams = ecKey.getParameters();

            ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
            if (s.isInfinity()) {
                throw new IllegalArgumentException("invalid key: [h]Q at infinity");
            }

            random = rParam.getRandom();
        } else {
            ecKey = (ECKeyParameters) param;
            ecParams = ecKey.getParameters();
        }

        curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
    }

    public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        if (forEncryption) {
            return encrypt(in, inOff, inLen);
        } else {
            return decrypt(in, inOff, inLen);
        }
    }

    public int getOutputSize(int inputLen) {
        return (1 + 2 * curveLength) + inputLen + digest.getDigestSize();
    }

    protected ECMultiplier createBasePointMultiplier() {
        return new FixedPointCombMultiplier();
    }

    /**
     * 加密
     * 
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] encrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        byte[] c2 = new byte[inLen];

        System.arraycopy(in, inOff, c2, 0, c2.length);

        ECMultiplier multiplier = createBasePointMultiplier();

        ECPoint c1P;
        ECPoint kPB;
        do {
            BigInteger k = nextK();

            c1P = multiplier.multiply(ecParams.getG(), k).normalize();

            kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();

            kdf(digest, kPB, c2);
        } while (notEncrypted(c2, in, inOff));

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, kPB.getAffineXCoord());
        digest.update(in, inOff, inLen);
        addFieldElement(digest, kPB.getAffineYCoord());

        digest.doFinal(c3, 0);

        return convertToASN1(c1P, c2, c3);
    }

    /**
     * 解密
     * 
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] decrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        byte[] decryptData = new byte[inLen];
        System.arraycopy(in, inOff, decryptData, 0, decryptData.length);

        BigInteger x;
        BigInteger y;
        byte[] originC3;
        byte[] c2;
        ECPoint c1P;
        byte[] c1;
        try (ASN1InputStream aIn = new ASN1InputStream(decryptData)) {
            ASN1Sequence seq;
            try {
                seq = (ASN1Sequence) aIn.readObject();
            } catch (IOException e) {
                throw new InvalidCipherTextException();
            }
            x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
            y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
            c1P = ecParams.getCurve().validatePoint(x, y);
            c1 = c1P.getEncoded(false);
            if (mode == Mode.C1C3C2) {
                originC3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
                c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
            } else {
                c2 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
                originC3 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
            }
        } catch (IOException e) {
            throw new InvalidCipherTextException();
        }

        ECPoint s = c1P.multiply(ecParams.getH());
        if (s.isInfinity()) {
            throw new InvalidCipherTextException("[h]C1 at infinity");
        }

        c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();

        kdf(digest, c1P, c2);

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, c1P.getAffineXCoord());
        digest.update(c2, 0, c2.length);
        addFieldElement(digest, c1P.getAffineYCoord());

        digest.doFinal(c3, 0);

        int check = 0;
        for (int i = 0; i != c3.length; i++) {
            check |= c3[i] ^ originC3[i];
        }

        Arrays.fill(c1, (byte) 0);
        Arrays.fill(c3, (byte) 0);

        if (check != 0) {
            Arrays.fill(c2, (byte) 0);
            throw new InvalidCipherTextException("invalid cipher text");
        }

        return c2;
    }

    private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
        for (int i = 0; i != encData.length; i++) {
            if (encData[i] != in[inOff + i]) {
                return false;
            }
        }

        return true;
    }

    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
        int digestSize = digest.getDigestSize();
        byte[] buf = new byte[Math.max(4, digestSize)];
        int off = 0;

        Memoable memo = null;
        Memoable copy = null;

        if (digest instanceof Memoable) {
            addFieldElement(digest, c1.getAffineXCoord());
            addFieldElement(digest, c1.getAffineYCoord());
            memo = (Memoable) digest;
            copy = memo.copy();
        }

        int ct = 0;

        while (off < encData.length) {
            if (memo != null) {
                memo.reset(copy);
            } else {
                addFieldElement(digest, c1.getAffineXCoord());
                addFieldElement(digest, c1.getAffineYCoord());
            }

            Pack.intToBigEndian(++ct, buf, 0);
            digest.update(buf, 0, 4);
            digest.doFinal(buf, 0);

            int xorLen = Math.min(digestSize, encData.length - off);
            xor(encData, buf, off, xorLen);
            off += xorLen;
        }
    }

    private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
        for (int i = 0; i != dRemaining; i++) {
            data[dOff + i] ^= kdfOut[i];
        }
    }

    private BigInteger nextK() {
        int qBitLength = ecParams.getN().bitLength();

        BigInteger k;
        do {
            k = BigIntegers.createRandomBigInteger(qBitLength, random);
        } while (k.equals(BigIntegers.ZERO) || k.compareTo(ecParams.getN()) >= 0);

        return k;
    }

    private void addFieldElement(Digest digest, ECFieldElement v) {
        byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());

        digest.update(p, 0, p.length);
    }

    private byte[] convertToASN1(ECPoint c1P, byte[] c2, byte[] c3) {
        ASN1Integer x = new ASN1Integer(c1P.getXCoord().toBigInteger());
        ASN1Integer y = new ASN1Integer(c1P.getYCoord().toBigInteger());
        DEROctetString derDig = new DEROctetString(c3);
        DEROctetString derEnc = new DEROctetString(c2);
        ASN1EncodableVector v = new ASN1EncodableVector();
        switch (mode) {
        case C1C3C2:
            v.add(x);
            v.add(y);
            v.add(derDig);
            v.add(derEnc);
            break;
        default:
            v.add(x);
            v.add(y);
            v.add(derEnc);
            v.add(derDig);
        }
        DERSequence seq = new DERSequence(v);
        try {
            return seq.getEncoded();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
新建Sm2Utils工具類
package com.zhaohy.app.utils;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * SM2工具類
 */
public class Sm2Utils {
    
    /**
     * 加簽
     * @param plainText
     * @return
     */
    public static String sign(String plainText, String privateKeyStr) {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        try {   
            // 獲取橢圓曲線KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
            Signature rsaSignature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
            rsaSignature.initSign(keyFactory.generatePrivate(privateKeySpec));
            rsaSignature.update(plainText.getBytes());
            byte[] signed = rsaSignature.sign();
            return Base64.getEncoder().encodeToString(signed);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 驗簽
     * @param plainText
     * @param signatureValue
     * @return
     */
    public static boolean verify(String plainText, String signatureValue, String publicKeyStr) {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        try {
            // 獲取橢圓曲線KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
            // 初始化為驗簽狀態(tài)
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
            signature.initVerify(keyFactory.generatePublic(publicKeySpec));
            signature.update(Hex.decodeHex(plainText.toCharArray()));
            return signature.verify(Hex.decodeHex(signatureValue.toCharArray()));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            LogUtils.error("驗簽失敗", e);
            return false;
        } catch (DecoderException e) {
            LogUtils.error("驗簽失敗", e);
            return false;
        }
    }
    
    /**
     * 加密
     * @param plainText
     * @return
     */
    public static byte[] encrypt(String plainText, String publicKeyStr) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        try {   
            // 獲取橢圓曲線KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
            CipherParameters publicKeyParamerters = ECUtil.generatePublicKeyParameter(publicKey);
            //數(shù)據(jù)加密
            StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(true, new ParametersWithRandom(publicKeyParamerters));
            byte[] encryptData = engine.processBlock(plainText.getBytes(), 0, plainText.getBytes().length);
            return encryptData;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException 
                | InvalidKeyException | InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 解密
     * @param encryptedText
     * @return
     */
    public static String decrypt(byte[] encryptedData, String privateKeyStr) {
        Security.addProvider(new BouncyCastleProvider());
        try {   
            // 獲取橢圓曲線KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
            CipherParameters privateKeyParamerters = ECUtil.generatePrivateKeyParameter(privateKey);
            //數(shù)據(jù)解密
            StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(false, privateKeyParamerters);
            byte[] plainText = engine.processBlock(encryptedData, 0, encryptedData.length);
            return new String(plainText);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException 
                | InvalidKeyException | InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * SM2算法生成密鑰對
     * @return 密鑰對信息
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 獲取一個橢圓曲線類型的密鑰對生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            SecureRandom random = new SecureRandom();
            // 使用SM2的算法區(qū)域初始化密鑰生成器
            kpg.initialize(sm2Spec, random);
            // 獲取密鑰對
            KeyPair keyPair = kpg.generateKeyPair();
            return keyPair;
        } catch (Exception e) {
            LogUtils.error("generate sm2 key pair failed:{}", e.getMessage(), e);
            return null;
        }
    }
    
    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateSm2KeyPair();
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        String publicKey  = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String data = "{\"daId\":\"123456\"}";
        String encryptedJsonStr =  Hex.encodeHexString(encrypt(data, publicKey)) + "";//16進制字符串
        String decryptedJsonStr = decrypt(Hex.decodeHex(encryptedJsonStr), privateKey);
        String sign = Hex.encodeHexString(Base64.getDecoder().decode(sign(data, privateKey)));
        boolean flag = verify(Hex.encodeHexString(data.getBytes()), sign, publicKey);
        System.out.println("base64后privateKey:" + privateKey);
        System.out.println("base64后publicKey:" + publicKey);
        System.out.println("加密前數(shù)據(jù):" + data);
        System.out.println("公鑰加密后16進制字符串:" + encryptedJsonStr);
        System.out.println("私鑰解密后數(shù)據(jù):" + decryptedJsonStr);
        System.out.println("私鑰加簽后數(shù)據(jù)(16進制):" + sign);
        System.out.println("公鑰驗簽結果:" + flag);
        
    }

}

可以看到工具類里已經(jīng)寫好了調用測試main方法,運行結果如下:

base64后privateKey:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgmze2hmNoi3XpeMPmLNIQBOC7+P1iMbwGMMAdQXko0VWgCgYIKoEcz1UBgi2hRANCAATyF9jHsCvFaLkR4DS+viX9CShZEXc7Nc1OueDaIpzZx/DRTRejSpTOcFmb0B9TsyYutnRWAx46nfkQ289NVXjg
base64后publicKey:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE8hfYx7ArxWi5EeA0vr4l/QkoWRF3OzXNTrng2iKc2cfw0U0Xo0qUznBZm9AfU7MmLrZ0VgMeOp35ENvPTVV44A==
加密前數(shù)據(jù):{"daId":"123456"}
公鑰加密后16進制字符串:307b022100f1ab8b4fc8755d2ab84bf530f15ab14f5250d060d52679fd38e822a85eeceb860221009eef85a579ef9e2f27f44461a89046b95f8c630773be7ced1f3f3f48057f4777042064d9b5fb396591e022791858e334fc40b1ee9c93412442d403385266a00dd78704117b30ed38afcb361f8176acbbfbabad71e9
私鑰解密后數(shù)據(jù):{"daId":"123456"}
私鑰加簽后數(shù)據(jù)(16進制):304402201505caa731d9b70987a6c19f40618593e9acd8cad880dc001e52ef4b48638e2302204bdb5c5dc881789e716de7360aa387e5e4caa9f9f2fd6d1fbce35f1227b34440
公鑰驗簽結果:true

可以看到加密解密加簽驗簽都是成功的北启。

上面加密后生成的是byte[]卜朗,轉換成16進制字符串處理的,也可以用base64實現(xiàn)byte[]和字符串的轉換處理咕村,但是要保證對應加解密都是用base64實現(xiàn)byte[]和字符串的轉換场钉,對應的上就行。

java實現(xiàn)byte[]和16進制字符串之間的轉換是通過Apache Commons Codec包來實現(xiàn)的培廓,一般springboot里有依賴這個惹悄,如果沒有,則maven引入:

<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末肩钠,一起剝皮案震驚了整個濱河市泣港,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌价匠,老刑警劉巖当纱,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踩窖,居然都是意外死亡坡氯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門洋腮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箫柳,“玉大人,你說我怎么就攤上這事啥供∶趸校” “怎么了?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵伙狐,是天一觀的道長涮毫。 經(jīng)常有香客問我瞬欧,道長,這世上最難降的妖魔是什么罢防? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任艘虎,我火速辦了婚禮,結果婚禮上咒吐,老公的妹妹穿的比我還像新娘野建。我一直安慰自己,他們只是感情好渤滞,可當我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布贬墩。 她就那樣靜靜地躺著,像睡著了一般妄呕。 火紅的嫁衣襯著肌膚如雪陶舞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天绪励,我揣著相機與錄音肿孵,去河邊找鬼。 笑死疏魏,一個胖子當著我的面吹牛停做,可吹牛的內容都是我干的。 我是一名探鬼主播大莫,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蛉腌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了只厘?” 一聲冷哼從身側響起烙丛,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤政己,失蹤者是張志新(化名)和其女友劉穎紊搪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舌界,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡赋元,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年忘蟹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搁凸。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡媚值,死狀恐怖,靈堂內的尸體忽然破棺而出护糖,到底是詐尸還是另有隱情杂腰,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布椅文,位于F島的核電站喂很,受9級特大地震影響,放射性物質發(fā)生泄漏皆刺。R本人自食惡果不足惜少辣,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羡蛾。 院中可真熱鬧漓帅,春花似錦、人聲如沸痴怨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浪藻。三九已至捐迫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爱葵,已是汗流浹背施戴。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萌丈,地道東北人赞哗。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像辆雾,于是被迫代替她去往敵國和親肪笋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內容