前言

需求:需要在NDK层对一个Java层的字符串进行RSA加密,然后对加密的结果进行Base64返回到Java层
方案:选择使用OpenSSL来实现。

编译libssl.a和libcrypto.a静态库

在github上找到了一个项目,可以直接将OpenSSL编译成Android可以使用的,项目地址为

  • openssl_for_ios_and_android

但是这个项目有点小问题,部分编译脚本需要做点改动,改动后的项目见

  • openssl_for_ios_and_android

主要做了3个改动:

  1. 将最低版本支持从Android 21改到了Android 14
  2. 修复一个armeabi-v7a无法编译出来的问题
  3. 升级了openssl的版本到openssl-1.1.0e

之后将项目clone下来,进入到tools目录,执行build-openssl4android.sh编译脚本

1
2
./build-openssl4android.sh android-armeabi armeabi-v7a
./build-openssl4android.sh android armeabi

这里只编译了armeabi-va7和armeabi架构CPU的so,如果有需要,请自行更改命令参数编译X86等架构的so。

经过很长时间的编译。。。大概要10来分钟吧。。。在根目录下的output会产生一个android目录,里面有openssl-armeabi和openssl-armeabi-v7a两个文件夹,包含了openssl的头文件以及编译好的.a静态库

实现JNI函数

编译好后.a静态库,就可以创建jni项目了

进入jni项目根目录,创建Application.mk文件

1
2
3
4
5
6
7
8
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-14
APP_OPTIM := release
APP_STL := c++_static
APP_THIN_ARCHIVE := true
APP_CPPFLAGS := -fpic -fexceptions -frtti
APP_GNUSTL_FORCE_CPP_FEATURES := pic exceptions rtti

进入jni项目根目录,创建Android.mk文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
LOCAL_PATH := $(call my-dir)
#引用libcrypto.a
include $(CLEAR_VARS)
LOCAL_MODULE := libcrypto
LOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
#引用libssl.a
include $(CLEAR_VARS)
LOCAL_MODULE := libssl
LOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libssl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := \
native.cpp \
LOCAL_C_INCLUDES :=$(LOCAL_PATH)/openssl/openssl-$(TARGET_ARCH_ABI)/include
TARGET_PLATFORM := android-14
#静态库依赖
LOCAL_STATIC_LIBRARIES := libssl libcrypto
LOCAL_LDLIBS += -latomic -lz -llog
include $(BUILD_SHARED_LIBRARY)

进入jni项目根目录,拷贝编译好的openssl文件

接着将第一步编译好的静态库文件进行拷贝,将output目录下android整个目录进行拷贝,拷贝到jni项目根目录下,拷贝完成后将android目录重命名为openssl

进入jni项目根目录,创建native.cpp,搭建基础的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "jni.h"
template<typename T, int N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define NELEMS(x) (sizeof(ArraySizeHelper(x)))
#ifndef CLASSNAME
#define CLASSNAME "com/fucknmb/Test"
#endif
jstring native_rsa(JNIEnv *env, jobject thiz, jstring base64PublicKey, jstring content) {
return NULL;
}
static const JNINativeMethod sMethods[] = {
{
const_cast<char *>("native_rsa"),
const_cast<char *>("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
reinterpret_cast<void *>(native_rsa)
}
};
int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *methods,
const int numMethods) {
jclass clazz = env->FindClass(className);
if (!clazz) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, methods, numMethods) != 0) {
env->DeleteLocalRef(clazz);
return JNI_FALSE;
}
env->DeleteLocalRef(clazz);
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
registerNativeMethods(env, CLASSNAME, sMethods, NELEMS(sMethods));
return JNI_VERSION_1_6;
}

声明java层函数

在Java层创建com/fucknmb/Test类,声明一个native函数

1
2
3
4
5
6
7
8
9
10
11
package com.fucknmb;
import java.util.List;
public class Test {
public static native final String native_rsa(String base64PublicKey, String content);
static {
System.loadLibrary("test");
}
}

实现native_rsa函数

native_rsa函数有两个参数,一个是base64之后的公钥(不含头部和尾部,以及没换行),第二个是待加密的明文内容,该函数的返回值是加密后的密文进行base64。

对于第一个参数,我们需要将其转为公钥文件字符串,追加头部和尾部,其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据公钥base64字符串(没换行)生成公钥文本内容
* @param base64EncodedKey
* @return
*/
std::string generatePublicKey(std::string base64EncodedKey) {
std::string publicKey = base64EncodedKey;
size_t base64Length = 64;//每64个字符一行
size_t publicKeyLength = base64EncodedKey.size();
for (size_t i = base64Length; i < publicKeyLength; i += base64Length) {
//每base64Length个字符,增加一个换行
if (base64EncodedKey[i] != '\n') {
publicKey.insert(i, "\n");
}
i++;
}
//最前面追加公钥begin字符串
publicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n");
//最前面追加公钥end字符串
publicKey.append("\n-----END PUBLIC KEY-----");
return publicKey;
}

openssl rsa加密后,我们需要对密文进行Base64,openssl同样提供了Base64算法,实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* base64 encode
* @param decoded_bytes
* @return
*/
std::string base64_encode(const std::string &decoded_bytes) {
BIO *bio, *b64;
BUF_MEM *bufferPtr;
b64 = BIO_new(BIO_f_base64());
//不换行
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
//encode
BIO_write(bio, decoded_bytes.c_str(), (int) decoded_bytes.length());
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
//这里的第二个参数很重要,必须赋值
std::string result(bufferPtr->data, bufferPtr->length);
BIO_free_all(bio);
return result;
}

这个函数有一点需要注意的就是这一行

1
std::string result(bufferPtr->data, bufferPtr->length);

第二个参数表示长度,不能少,否则base64后的字符串长度会出现异常,导致decode的时候末尾会出现一大堆的乱码,而网上大多数的代码,是缺失这一个参数的。

接下来就是rsa的实现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 使用公钥对明文加密
* @param publicKey
* @param from
* @return
*/
std::string encryptRSA(const std::string &publicKey, const std::string &from) {
BIO *bio = NULL;
RSA *rsa_public_key = NULL;
//从字符串读取RSA公钥串
if ((bio = BIO_new_mem_buf((void *) publicKey.c_str(), -1)) == NULL) {
std::cout << "BIO_new_mem_buf failed!" << std::endl;
return "";
}
//读取公钥
rsa_public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
//异常处理
if (rsa_public_key == NULL) {
//资源释放
BIO_free_all(bio);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return "";
}
//rsa模的位数
int rsa_size = RSA_size(rsa_public_key);
//RSA_PKCS1_PADDING 最大加密长度 为 128 -11
//RSA_NO_PADDING 最大加密长度为 128
//rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE;
//动态分配内存,用于存储加密后的密文
unsigned char *to = (unsigned char *) malloc(rsa_size + 1);
//填充0
memset(to, 0, rsa_size + 1);
//明文长度
int flen = from.length();
//加密,返回值为加密后的密文长度,-1表示失败
int status = RSA_public_encrypt(flen, (const unsigned char *) from.c_str(), to, rsa_public_key,
RSA_PKCS1_PADDING);
//异常处理
if (status < 0) {
//资源释放
free(to);
BIO_free_all(bio);
RSA_free(rsa_public_key);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return "";
}
//赋值密文
static std::string result((char *) to, status);
//资源释放
free(to);
BIO_free_all(bio);
RSA_free(rsa_public_key);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return result;
}

同样这个函数也有几个地方需要注意:

第一点:

1
static std::string result((char *) to, status);

第二个参数表示密文长度,一般来说,这个值会是128,如果第二个值不传,会导致加密后的密文经过string的构造函数后,丢失一部分数据,导致数据的不正确

第二点:

1
rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE;

对于RSA_PKCS1_PADDING_SIZE,最大加密长度为需要减去11

2017.7.17修改,第二点经过试验,废弃!

第三点:

1
2
3
4
5
6
//明文长度
int flen = from.length();
//加密,返回值为加密后的密文长度,-1表示失败
int status = RSA_public_encrypt(flen, (const unsigned char *) from.c_str(), to, rsa_public_key,
RSA_PKCS1_PADDING);

RSA_public_encrypt函数的第一个参数传的是明文长度,而不是最大加密长度rsa_size,网上的所有代码这个参数都是传错的,传了rsa_size,而实际上这个参数的参数名是flen,表示from字符串的length。如果这个参数传了最大加密长度,将直接导致java层无法正确解密JNI层加密后的数据。

最后不要忘记加头文件的引用

1
2
3
4
5
6
7
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <iostream>
using std::string;

需要的函数都有了,实现以下native_rsa函数,简单组装一下以上函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jstring native_rsa(JNIEnv *env, jobject thiz, jstring base64PublicKey, jstring content) {
//jstring 转 char*
char *base64PublicKeyChars = (char *) env->GetStringUTFChars(base64PublicKey, NULL);
//char* 转 string
string base64PublicKeyString = string(base64PublicKeyChars);
//生成公钥字符串
string generatedPublicKey = generatePublicKey(base64PublicKeyString);
//释放
env->ReleaseStringUTFChars(base64PublicKey, base64PublicKeyChars);
//jstring 转 char*
char *contentChars = (char *) env->GetStringUTFChars(content, NULL);
//char* 转 string
string contentString = string(contentChars);
//释放
env->ReleaseStringUTFChars(content, contentChars);
//调用RSA加密函数加密
string rsaResult = encryptRSA(generatedPublicKey, contentString);
if (rsaResult.empty()) {
return NULL;
}
//将密文进行base64
string base64RSA = base64_encode(rsaResult);
if (base64RSA.empty()) {
return NULL;
}
//string -> char* -> jstring 返回
jstring result = env->NewStringUTF(base64RSA.c_str());
return result;
}

私钥解密

如果你还需要用的私钥解密部分,可以继续实现base64的decode函数,以及rsa的私钥串生成函数,rsa的解密函数

base64 decode函数的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* base64 decode
* @param encoded_bytes
* @return
*/
std::string base64_decode(const std::string &encoded_bytes) {
BIO *bioMem, *b64;
bioMem = BIO_new_mem_buf((void *) encoded_bytes.c_str(), -1);
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bioMem = BIO_push(b64, bioMem);
//获得解码长度
size_t buffer_length = BIO_get_mem_data(bioMem, NULL);
char *decode = (char *) malloc(buffer_length + 1);
//填充0
memset(decode, 0, buffer_length + 1);
BIO_read(bioMem, (void *) decode, (int) buffer_length);
static std::string decoded_bytes(decode);
BIO_free_all(bioMem);
return decoded_bytes;
}

rsa的私钥串生成函数的试下如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据私钥base64字符串(没换行)生成私钥文本内容
* @param base64EncodedKey
* @return
*/
std::string generatePrivateKey(std::string base64EncodedKey) {
std::string privateKey = base64EncodedKey;
size_t base64Length = 64;//每64个字符一行
size_t privateKeyLength = base64EncodedKey.size();
for (size_t i = base64Length; i < privateKeyLength; i += base64Length) {
//每base64Length个字符,增加一个换行
if (base64EncodedKey[i] != '\n') {
privateKey.insert(i, "\n");
}
i++;
}
//最前面追加私钥begin字符串
privateKey.insert(0, "-----BEGIN PRIVATE KEY-----\n");
//最后面追加私钥end字符串
privateKey.append("\n-----END PRIVATE KEY-----");
return privateKey;
}

私钥解密函数的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 使用私钥对密文解密
* @param privetaKey
* @param from
* @return
*/
std::string decryptRSA(const std::string &privetaKey, const std::string &from) {
BIO *bio = NULL;
RSA *rsa_private_key = NULL;
//从字符串读取RSA公钥串
if ((bio = BIO_new_mem_buf((void *) privetaKey.c_str(), -1)) == NULL) {
std::cout << "BIO_new_mem_buf failed!" << std::endl;
return "";
}
//读取私钥
rsa_private_key = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
//异常处理
if (rsa_private_key == NULL) {
//资源释放
BIO_free_all(bio);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return "";
}
//rsa模的位数
int rsa_size = RSA_size(rsa_private_key);
//动态分配内存,用于存储解密后的明文
unsigned char *to = (unsigned char *) malloc(rsa_size + 1);
//填充0
memset(to, 0, rsa_size + 1);
//密文长度
int flen = from.length();
// RSA_NO_PADDING
// RSA_PKCS1_PADDING
//解密,返回值为解密后的名文长度,-1表示失败
int status = RSA_private_decrypt(flen, (const unsigned char *) from.c_str(), to, rsa_private_key,
RSA_PKCS1_PADDING);
//异常处理率
if (status < 0) {
//释放资源
free(to);
BIO_free_all(bio);
RSA_free(rsa_private_key);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return "";
}
//赋值明文,是否需要指定to的长度?
static std::string result((char *) to);
//释放资源
free(to);
BIO_free_all(bio);
RSA_free(rsa_private_key);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
return result;
}

如果你要解密公钥加密后的密文,只需要这样调用即可返回明文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//公钥串和私钥串
string generatedPublicKey = generatePublicKey(base64PublicKey);
string generatedPrivetKey = generatePrivateKey(base64PrivateKey);
string content("just a test");
//加密
string result = encryptRSA(generatedPublicKey, content);
//encode
string base64RSA = base64_encode(result);
//decode
string decodeBase64RSA = base64_decode(base64RSA);
//解密
string origin = decryptRSA(generatedPrivetKey, decodeBase64RSA);

最后注意一下base64PublicKey和base64PrivateKey,这两个字符串是不包含换行的,就是私钥和公钥的encoded之后的字节数组base64后的值,因此需要自己调用generatePublicKey和generatePrivateKey追加头和尾。

RSA公钥和私钥的生成

生成私钥

1
openssl genrsa -out rsa_private_key.pem 1024

这条命令让openssl随机生成了一份私钥,加密长度是1024位。加密长度是指理论上最大允许”被加密的信息“长度的限制,也就是明文的长度限制。随着这个参数的增大(比方说2048),允许的明文长度也会增加,但同时也会造成计算复杂度的极速增长。一般推荐的长度就是1024位(128字节,之前的代码的最大加密长度128就是这么来的)。

生成公钥

1
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout

密钥文件最终将数据通过Base64编码进行存储。可以看到上述生成的密钥文件内容每一行的长度都很规律。这是由于RFC2045中规定:The encoded output stream must be represented in lines of no more than 76 characters each。也就是说Base64编码的数据每行最多不超过76字符,对于超长数据需要按行分割。

上面的generatePublicKey和generatePrivateKey函数我们是按64位一行进行分割的,如果你有需要,可以将值修改为76。

第一步生成私钥文件不能直接使用,需要进行PKCS#8编码:

1
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt

第二步和第三步生成的公钥和私钥就可以用了,这里有个问题需要注意,如果你的公钥和私钥是类似下面这种格式的

1
2
3
4
5
6
7
8
-----BEGIN PUBLIC KEY-----
....
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
....
-----END PRIVATE KEY-----

那么,你无需调用generatePublicKey或者generatePrivateKey函数,此时已经是需要的公钥串和私钥串,但是如果你的公钥和私钥没有头部和尾部,并且不是换行的,就需要调用一下进行转换,因为我这边Java层传入的是后者,所以需要调用generatePublicKey或者generatePrivateKey进行转换。

Java层调用公钥加密函数部分

1
2
String base64PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP0tzYxBF5IGfNvuIHzAqvza/ZxfH8aEiPFA4nY/W3js+cG3JUU86Jkc7jUG9XfGdW6SJ38ANs5tyWqYkJyoUErB2PjQQQDmHhbgpBUSeOdwGr/LPtrTrotrNXwpRY9eodkcbcMlbT0gvdnohRSISCjJ2KmFcBMkeO9R2DWe6oIwIDAQAB";
String result = com.fucknmb.Test.native_rsa(base64PublicKey,"I am test");
http://fucknmb.com/2017/04/09/Android%E5%9C%A8NDK%E5%B1%82%E4%BD%BF%E7%94%A8OpenSSL%E8%BF%9B%E8%A1%8CRSA%E5%8A%A0%E5%AF%86/

Android 在 NDK 层使用 OpenSSL 进行 RSA 加密相关推荐

  1. 使用OpenSSL进行RSA加密和解密(非对称)

    1. RSA加密和解密基础概念 RSA是一种非对称加密. RSA秘钥:私钥和公钥,一对私钥和公钥就像夫妻一样是唯一的,用私钥加密后必须用对应的公钥才能解密,用公钥加密后必须用对应的私钥才能解密. 加密 ...

  2. php java openssl ras_php基于openssl的rsa加密解密示例

    本文实例讲述了php基于openssl的rsa加密解密.分享给大家供大家参考,具体如下: $config = array( //"config" =>"D:/php ...

  3. C++使用OPENSSL进行RSA加密,java服务端解密

    RSA是一种非对称加密. 加密和解密方式:公钥加密-私钥解密,私钥加密-公钥解密 背景 为了网络数据安全,Web端(Java)要求用RSA加密算法传数据,公钥加密私钥解密方式(RSA有公钥加密私钥解密 ...

  4. OpenSSL 编程 - RSA 加密解密

    http://blog.csdn.net/nadoo/article/details/2156384 这几天做这方面的东西,网上资料很少,贴一个自己试验写的代码,做个记录. 加密: /* gcc -o ...

  5. jsencrypt代码分析——openssl的rsa加密解密在js的实现

    在js上做rsa,感觉jsencrypt这个是封装的比较好的,但用起来还是遇到了些坑,所以踩进代码里填填坑- 项目在这里 https://github.com/travist/jsencrypt [r ...

  6. Linux的rsa命令,openssl命令行进行RSA加密解密

    openssl是一个功能强大的工具包,它集成了众多密码算法及实用工具.我们即可以利用它提供的命令台工具生成密钥.证书来加密解密文件,也可以在利用其提供的API接口在代码中对传输信息进行加密. RSA是 ...

  7. RSA加密解密DES加密解密AES

    文章目录 RSA java 依赖 RSACoder RSACoderTest js示例 DES 示例一 js java 示例2 js java AES AesUtil RSA java 依赖 < ...

  8. python rsa加密解密_RSA加密解密(python版)

    RSA的算法涉及三个参数,n.e.d. 其中,n是两个大质数p.q的积,n的二进制表示时所占用的位数,就是所谓的密钥长度. e1和d是一对相关的值,e可以任意取,但要求e与(p-1)*(q-1)互质: ...

  9. 在Android NDK中使用OpenSSL

    从 6.0 开始,Google 要求不要使用系统的 OpenSSL,请见:https://developer.android.com....因此,请不要再使用本文介绍的方法,请自行交叉编译 OpenS ...

最新文章

  1. 2021 年 ICT 行业预测
  2. Asp.net 服务器端控件
  3. Spring Cloud构建微服务架构:服务消费(Feign)【Dalston版】
  4. 腐烂国度2怎么学计算机,腐烂国度2按键操作说明 腐烂国度2怎么操作
  5. 程序员谈敏捷开发团队里成员间的相互信任
  6. 开源贡献 计算_使用此网站为开源做贡献
  7. centos mysql导出数据库命令_在centos(linux)下用命令导出mysql数据库数据
  8. Python工作笔记004---python字符串前面加上'r'的作用_u_b的含义
  9. 前端框架——Jquery——基础篇7__工具函数(Utils)
  10. java实现获取中国大学名称列表、即所在省份
  11. Win7 Hotfix KB2685811 (64-bit)疑难杂症状之解药
  12. PHP获取今日农历日期
  13. Chrome去除新标签页最近访问
  14. 使用Springboot+MAVEN完成SSM项目的搭建(idea)--小白面试机试题
  15. 云端服务器的稳定方法,云端服务器的问题怎么解决
  16. 小程序源码:宝宝起名神器微信小程序源码下载-多玩法安装简单
  17. 【分享】微信公众号在 “集简云平台“ 集成应用的常见问题与解决方案
  18. 用Filmage Editor制作视频,赢不限时长录屏神器永久序列码!
  19. Linux防火墙操作命令,开放或关闭端口
  20. webots车轮下陷问题 车轮在地下

热门文章

  1. ubuntu-桌面版-常用设置
  2. Python的初级语法
  3. 判断是否是闰年的方法,很简单噢
  4. 解析并验证IE6及之前版本的'!important’ BUG(转)
  5. tensorflow中的关键字global_step使用
  6. 在C/C++中嵌入Python
  7. list::splice函数的用法与参数解释
  8. 计算若干数据的汉明距离总和
  9. 【图像】Dog(高斯差分)检测角点
  10. [云炬创业基础笔记]第六章商业模式测试1