Python調(diào)用Java接口實現(xiàn)互相SM2加簽驗簽
環(huán)境極其依賴
python環(huán)境
pip3 install gmssl
java環(huán)境
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15to18</artifactId> <version>1.66</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.18</version> </dependency>
具體功能
1.Python生成密鑰對
from gmssl import sm2 as SM2 from gmssl import func as GMFunc from random import SystemRandom from base64 import b64encode, b64decode class CurveFp: def __init__(self, A, B, P, N, Gx, Gy, name): self.A = A self.B = B self.P = P self.N = N self.Gx = Gx self.Gy = Gy self.name = name class SM2Key: sm2p256v1 = CurveFp( name="sm2p256v1", A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC, B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93, P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF, N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123, Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7, Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0 ) @staticmethod def multiply(a, n, N, A, P): return SM2Key.fromJacobian(SM2Key.jacobianMultiply(SM2Key.toJacobian(a), n, N, A, P), P) @staticmethod def add(a, b, A, P): return SM2Key.fromJacobian(SM2Key.jacobianAdd(SM2Key.toJacobian(a), SM2Key.toJacobian(b), A, P), P) @staticmethod def inv(a, n): if a == 0: return 0 lm, hm = 1, 0 low, high = a % n, n while low > 1: r = high // low nm, new = hm - lm * r, high - low * r lm, low, hm, high = nm, new, lm, low return lm % n @staticmethod def toJacobian(Xp_Yp): Xp, Yp = Xp_Yp return Xp, Yp, 1 @staticmethod def fromJacobian(Xp_Yp_Zp, P): Xp, Yp, Zp = Xp_Yp_Zp z = SM2Key.inv(Zp, P) return (Xp * z ** 2) % P, (Yp * z ** 3) % P @staticmethod def jacobianDouble(Xp_Yp_Zp, A, P): Xp, Yp, Zp = Xp_Yp_Zp if not Yp: return 0, 0, 0 ysq = (Yp ** 2) % P S = (4 * Xp * ysq) % P M = (3 * Xp ** 2 + A * Zp ** 4) % P nx = (M ** 2 - 2 * S) % P ny = (M * (S - nx) - 8 * ysq ** 2) % P nz = (2 * Yp * Zp) % P return nx, ny, nz @staticmethod def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P): Xp, Yp, Zp = Xp_Yp_Zp Xq, Yq, Zq = Xq_Yq_Zq if not Yp: return Xq, Yq, Zq if not Yq: return Xp, Yp, Zp U1 = (Xp * Zq ** 2) % P U2 = (Xq * Zp ** 2) % P S1 = (Yp * Zq ** 3) % P S2 = (Yq * Zp ** 3) % P if U1 == U2: if S1 != S2: return 0, 0, 1 return SM2Key.jacobianDouble((Xp, Yp, Zp), A, P) H = U2 - U1 R = S2 - S1 H2 = (H * H) % P H3 = (H * H2) % P U1H2 = (U1 * H2) % P nx = (R ** 2 - H3 - 2 * U1H2) % P ny = (R * (U1H2 - nx) - S1 * H3) % P nz = (H * Zp * Zq) % P return nx, ny, nz @staticmethod def jacobianMultiply(Xp_Yp_Zp, n, N, A, P): Xp, Yp, Zp = Xp_Yp_Zp if Yp == 0 or n == 0: return (0, 0, 1) if n == 1: return (Xp, Yp, Zp) if n < 0 or n >= N: return SM2Key.jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P) if (n % 2) == 0: return SM2Key.jacobianDouble(SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P) if (n % 2) == 1: mv = SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P) return SM2Key.jacobianAdd(SM2Key.jacobianDouble(mv, A, P), (Xp, Yp, Zp), A, P) class PrivateKey: def __init__(self, curve=SM2Key.sm2p256v1, secret=None): self.curve = curve self.secret = secret or SystemRandom().randrange(1, curve.N) def PublicKey(self): curve = self.curve xPublicKey, yPublicKey = SM2Key.multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N) return PublicKey(xPublicKey, yPublicKey, curve) def ToString(self): return "{}".format(str(hex(self.secret))[2:].zfill(64)) class PublicKey: def __init__(self, x, y, curve): self.x = x self.y = y self.curve = curve def ToString(self, compressed=True): return '04' + { True: str(hex(self.x))[2:], False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64)) }.get(compressed) class SM2Util: def __init__(self, pub_key=None, pri_key=None): self.pub_key = pub_key self.pri_key = pri_key self.sm2 = SM2.CryptSM2(public_key=self.pub_key, private_key=self.pri_key) def Encrypt(self, data): info = self.sm2.encrypt(data.encode()) return b64encode(info).decode() def Decrypt(self, data): info = b64decode(data.encode()) return self.sm2.decrypt(info).decode() def Sign(self, data): random_hex_str = GMFunc.random_hex(self.sm2.para_len) sign = self.sm2.sign(data.encode(), random_hex_str) return sign def Verify(self, data, sign): return self.sm2.verify(sign, data.encode()) @staticmethod def GenKeyPair(): pri = PrivateKey() pub = pri.PublicKey() return pri.ToString(), pub.ToString(compressed=False) def main(): """ 主函數(shù) :return: """ import random vs = '我是笨蛋' data = ''.join([vs[random.randint(0, len(vs) - 1)] for i in range(500)]) print('原數(shù)據(jù):{}'.format(data)) e = SM2Util.GenKeyPair() print('私鑰1:{} 公鑰1:{}', (e[0], e[1])) print('私鑰:{} 公鑰:{}'.format(e[0], e[1])) sm2 = SM2Util(pri_key=e[0], pub_key=e[1][2:]) sign = sm2.Sign(data) print('簽名:{} 驗簽:{}'.format(sign, sm2.Verify(data, sign))) cipher = sm2.Encrypt(data) print('加密:{}\n解密:{}'.format(cipher, sm2.Decrypt(cipher))) if __name__ == '__main__': main()
2.java生成密鑰對
package SM; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.BCUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.util.Base64; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; public class Keys { public static void main(String[] args) { String text = "我是笨蛋"; System.out.println("原文:" + text); SM2 sm2 = new SM2(); SM2Engine.Mode mode=SM2Engine.Mode.C1C2C3; sm2.setMode(mode); sm2.setDigest(new SM3Digest()); String privateKey = HexUtil.encodeHexStr(sm2.getD()); String publicKey = HexUtil.encodeHexStr(sm2.getQ(false)); System.out.println("privateKey:" + privateKey); System.out.println("publicKey:" + publicKey); }}
3.Python加簽驗簽
import gmssl.func as gmssl_func from baseutils.gmssl import sm2, func private_key = 'b9069975c3276fab170ce5ea643e635b8f52075e69e6162232ae01555ed12a31' public_key = '04f9ad444af8e31f993d96a644c6759ae8f9e5056068540eaa0e6d5f6b338d8ac4a7aac58170c1f18a7227c0dd72daee8b4e1e10d3db94aab6ab0fc5cac550f048' data = 'Hello, SM2!'.encode("utf-8") hashdata = sm2.sm3.sm3_hash(func.bytes_to_list(data)) hashdata2 = bytes.fromhex(hashdata) print("hashdata----------", hashdata) signer = sm2.CryptSM2(private_key=private_key, public_key=public_key) # 簽名 random_hex = gmssl_func.random_hex(signer.para_len) signature = signer.sign(hashdata2, random_hex) verifier = sm2.CryptSM2(private_key=private_key, public_key=public_key) # 驗證簽名 is_valid = verifier.verify(signature, hashdata2) print("簽名數(shù)據(jù):", data) print("簽名:", signature) print("簽名驗證結果:", is_valid)
4.java加簽驗簽
package SM; import cn.hutool.core.lang.func.Func; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.SM2; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.util.Base64; /** * 國密非對稱加解密和加簽驗簽算法 * */ public class Sm2Test9 { public static void main(String[] args) { String text = "狗"; System.out.println("原文:" + text); KeyPair pair = SecureUtil.generateKeyPair("SM2"); byte[] privateKey = pair.getPrivate().getEncoded(); byte[] publicKey = pair.getPublic().getEncoded(); System.out.println("公鑰:\n" + bytesToBase64(publicKey)); System.out.println("私鑰:\n" + bytesToBase64(privateKey)); SM2 sm2 = SmUtil.sm2(privateKey, publicKey); //加簽 String sign = sm2.signHex(HexUtil.encodeHexStr(text)); System.out.println("簽名:" + sign); //驗簽 boolean verify = sm2.verifyHex(HexUtil.encodeHexStr(text), sign); System.out.println("驗簽:" + verify); /** * 驗簽私鑰 */ String signPrivateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgvIp+WfwJgOvvyPvfaxikVpRD5V5s2Z0hPo2a+GpfVzygCgYIKoEcz1UBgi2hRANCAARfv1UZ0Au40+P8bMqxCRaRx8VCc76S+UTTW2AaoO+5H+Z/XV96Dby1WulAGfOVoPdVpqb4rNcjKvrIjAujC+px"; String signPublicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEX79VGdALuNPj/GzKsQkWkcfFQnO+kvlE01tgGqDvuR/mf11feg28tVrpQBnzlaD3Vaam+KzXIyr6yIwLowvqcQ=="; String hexString1 = bytesToHex(base64ToBytes(signPrivateKey)); String hexString2 = bytesToHex(base64ToBytes(signPublicKey)); System.out.println("公鑰00:\n" + hexString2); System.out.println("私鑰00:\n" + hexString1); byte[] privateKey1 = base64ToBytes(signPrivateKey); byte[] publicKey1 = base64ToBytes(signPublicKey); SM2 sm22 = SmUtil.sm2(privateKey1, publicKey1); //加簽 String sign2 = sm22.signHex(HexUtil.encodeHexStr(text)); System.out.println("簽名111111:" + sign2); //驗簽 boolean verify2 = sm22.verifyHex(HexUtil.encodeHexStr(text), sign2); System.out.println("驗簽11111:" + verify2); } public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); } /** * 字節(jié)數(shù)組轉(zhuǎn)Base64編碼 * * @param bytes 字節(jié)數(shù)組 * @return Base64編碼 */ private static String bytesToBase64(byte[] bytes) { byte[] encodedBytes = Base64.getEncoder().encode(bytes); return new String(encodedBytes, StandardCharsets.UTF_8); } /** * Base64編碼轉(zhuǎn)字節(jié)數(shù)組 * * @param base64Str Base64編碼 * @return 字節(jié)數(shù)組 */ private static byte[] base64ToBytes(String base64Str) { byte[] bytes = base64Str.getBytes(StandardCharsets.UTF_8); return Base64.getDecoder().decode(bytes); } /** * 16進制字符串編碼轉(zhuǎn)字節(jié)數(shù)組 * * @param * @return 字節(jié)數(shù)組 */ public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } }
遇到的問題
python與java各自生成的密鑰對加簽驗簽沒有問題,但是互相驗簽失敗
python gmssl版本有影響
java hutool 生成的密鑰對的方法有很大區(qū)別,hutool本身也提供了幾種生成不同密鑰對的方法需要注意(30開頭的ASN1格式的密鑰好像目前python兼容不了)
解決方案
1.java兼容python(目前我提供的這個代碼是java生成的密鑰對python能用,python生成的密鑰對java也能用)
2.python兼容java(把java類打包成可執(zhí)行jar文件,在python中執(zhí)行)
import subprocess java_program = "java -jar D:\SM2Python\python_java\JARS\SM2.jar" result = subprocess.run(java_program.split(), stdout=subprocess.PIPE, universal_newlines=True) print(result.stdout)
3.搭建一個java項目可接口訪問
4.Java Hutool與python兼容的SM2簽名
import base64 import json from collections import OrderedDict from gmalg import SM2, SM3 class SignUtil: """SM2簽名工具類,嚴格遵循Java Hutool庫的簽名邏輯""" # Java Hutool SM2默認使用的UID DEFAULT_UID = b"1234567812345678" @staticmethod def _parse_private_key(der_data: bytes) -> bytes: """解析DER格式的SM2私鑰""" hex_data = der_data.hex() # 嘗試從PKCS#8格式解析私鑰 (120字節(jié)格式) key_pos = hex_data.find("0420") + 4 if key_pos > 3 and key_pos + 64 <= len(hex_data): return bytes.fromhex(hex_data[key_pos:key_pos + 64]) # 嘗試從另一種PKCS#8格式解析私鑰 (118字節(jié)格式) key_pos = hex_data.find("30740201010420") + 12 if key_pos > 12 and key_pos + 64 <= len(hex_data): return bytes.fromhex(hex_data[key_pos:key_pos + 64]) # 嘗試直接提取32字節(jié)私鑰 if len(hex_data) >= 64: return bytes.fromhex(hex_data[:64]) return b"" @staticmethod def _parse_public_key(der_data: bytes) -> bytes: """解析DER格式的SM2公鑰""" hex_data = der_data.hex() # 嘗試從X.509格式解析公鑰 (91字節(jié)格式) key_pos = hex_data.find("3059301306072a8648ce3d020106082a811ccf5501822d03420004") + 58 if key_pos > 58 and key_pos + 128 <= len(hex_data): return bytes.fromhex("04" + hex_data[key_pos:key_pos + 128]) # 嘗試從另一種X.509格式解析公鑰 (89字節(jié)格式) key_pos = hex_data.find("03420004") + 8 if key_pos > 8 and key_pos + 128 <= len(hex_data): return bytes.fromhex("04" + hex_data[key_pos:key_pos + 128]) # 嘗試直接提取65字節(jié)公鑰 if len(hex_data) >= 130 and hex_data.startswith("04"): return bytes.fromhex(hex_data[:130]) return b"" @staticmethod def sign(data: str, private_key_str: str) -> str: """ 生成與Java Hutool兼容的SM2簽名 :param data: 待簽名的原始數(shù)據(jù) :param private_key_str: Base64編碼的私鑰 :return: Base64編碼的簽名 """ try: print("=== Python 簽名調(diào)試信息 ===") print(f"data: {data}") # 解析私鑰 private_key_der = base64.b64decode(private_key_str) private_key_bytes = SignUtil._parse_private_key(private_key_der) if not private_key_bytes: raise ValueError("私鑰解析失敗") # 使用默認UID創(chuàng)建SM2實例 sm2 = SM2(sk=private_key_bytes, uid=SignUtil.DEFAULT_UID) # 計算SM3哈希(與Java Hutool一致,直接對數(shù)據(jù)進行SM3哈希,不包含Z值) data_bytes = data.encode('utf-8') sm3 = SM3() sm3.update(data_bytes) digest = sm3.value() print(f"SM3(data) (digest): {digest.hex()}") # 關鍵: Java簽名的是SM3哈希的hex字符串的字節(jié),而不是二進制digest # hashcode = SmUtil.sm3(data) -> 返回的是hex字符串 # hashcode.getBytes() -> 是這個hex字符串的UTF-8字節(jié) hashcode_hex_str = digest.hex() message_bytes = hashcode_hex_str.encode('utf-8') print(f"hashcode (hex string): {hashcode_hex_str}") print(f"hashcode.getBytes() (message_bytes): {message_bytes} (hex: {message_bytes.hex()})") # 生成簽名 r_bytes, s_bytes = sm2.sign(message_bytes) r_int = int.from_bytes(r_bytes, 'big') s_int = int.from_bytes(s_bytes, 'big') print(f"r (hex): {r_int.to_bytes((r_int.bit_length() + 7) // 8, 'big').hex()}") print(f"s (hex): {s_int.to_bytes((s_int.bit_length() + 7) // 8, 'big').hex()}") # 構建DER編碼簽名 from Cryptodome.Util.asn1 import DerSequence, DerInteger # 確保r和s是正數(shù) der_seq = DerSequence([ DerInteger(r_int), DerInteger(s_int) ]) signature_der = der_seq.encode() signature_b64 = base64.b64encode(signature_der).decode('utf-8') print(f"最終簽名 (Base64): {signature_b64}") print("=== 結束 Python 簽名調(diào)試信息 ===") return signature_b64 except Exception as e: print(f"簽名失敗: {e}") import traceback traceback.print_exc() return "" @staticmethod def verify(data: str, sign: str, public_key_str: str) -> bool: """ 驗證Java生成的SM2簽名 :param data: 原始數(shù)據(jù) :param sign: Base64編碼的簽名 :param public_key_str: Base64編碼的公鑰 :return: 驗簽結果 """ try: print("=== Python 驗證調(diào)試信息 ===") print(f"data: {data}") # 解析公鑰 public_key_der = base64.b64decode(public_key_str) public_key_bytes = SignUtil._parse_public_key(public_key_der) if not public_key_bytes: print("公鑰解析失敗") return False # 使用默認UID創(chuàng)建SM2實例 sm2 = SM2(pk=public_key_bytes, uid=SignUtil.DEFAULT_UID) # 計算SM3哈希 data_bytes = data.encode('utf-8') sm3 = SM3() sm3.update(data_bytes) digest = sm3.value() print(f"SM3(data) (digest): {digest.hex()}") # 關鍵: Java簽名的是SM3哈希的hex字符串的字節(jié) hashcode_hex_str = digest.hex() message_bytes = hashcode_hex_str.encode('utf-8') print(f"hashcode (hex string): {hashcode_hex_str}") print(f"hashcode.getBytes() (message_bytes): {message_bytes} (hex: {message_bytes.hex()})") # 解析簽名 sign_bytes = base64.b64decode(sign) print(f"簽名 (Base64): {sign}") print(f"簽名 (DER hex): {sign_bytes.hex()}") from Cryptodome.Util.asn1 import DerSequence seq = DerSequence() seq.decode(sign_bytes) r_int = seq[0] if isinstance(seq[0], int) else int.from_bytes(seq[0], 'big') s_int = seq[1] if isinstance(seq[1], int) else int.from_bytes(seq[1], 'big') r_bytes = r_int.to_bytes((r_int.bit_length() + 7) // 8, 'big') s_bytes = s_int.to_bytes((s_int.bit_length() + 7) // 8, 'big') print(f"r (hex): {r_bytes.hex()}") print(f"s (hex): {s_bytes.hex()}") # 驗證簽名 result = sm2.verify(message_bytes, r_bytes, s_bytes) print(f"驗簽結果: {result}") print("=== 結束 Python 驗證調(diào)試信息 ===") return result except Exception as e: print(f"驗簽失敗: {e}") import traceback traceback.print_exc() return False # 測試代碼 if __name__ == "__main__": # Java生成的密鑰對 java_private_key = "java私鑰(base64后的)" java_public_key = "java公鑰(base64后的)" java_signature = "java生成的簽名" # 創(chuàng)建要簽名的數(shù)據(jù)(與Java代碼完全一致) sign_data = OrderedDict() sign_data["data"] = "TEST" sign_data["version"] = "V1.0.0" data_to_sign = json.dumps(sign_data, separators=(',', ':')) print("待簽名數(shù)據(jù):", data_to_sign) print("數(shù)據(jù)長度:", len(data_to_sign)) print("數(shù)據(jù)HEX:", data_to_sign.encode('utf-8').hex()) # 驗證SM3哈希值 sm3 = SM3() sm3.update(data_to_sign.encode('utf-8')) digest = sm3.value() print("SM3哈希:", digest.hex()) # 使用Java私鑰在Python中生成簽名 print("\n=== Python生成簽名 ===") signature = SignUtil.sign(data_to_sign, java_private_key) print("Python生成的簽名:", signature) # 在Python中驗證Python生成的簽名 print("\n=== Python驗證Python簽名 ===") verify_result = SignUtil.verify(data_to_sign, signature, java_public_key) print("Python驗證Python簽名結果:", verify_result) # 在Python中驗證Java生成的簽名 print("\n=== Python驗證Java簽名 ===") java_verify_result = SignUtil.verify(data_to_sign, java_signature, java_public_key) print("Python驗證Java簽名結果:", java_verify_result) # 額外測試:使用Java公鑰驗證Java簽名(在Java中執(zhí)行) print("\n=== 請在Java中驗證以下數(shù)據(jù) ===") print("數(shù)據(jù):", data_to_sign) print("簽名:", signature)
到此這篇關于Python調(diào)用Java接口實現(xiàn)互相SM2加簽驗簽的文章就介紹到這了,更多相關SM2加簽驗簽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python 字符串處理特殊空格\xc2\xa0\t\n Non-breaking space
今天遇到一個問題,使用python的find函數(shù)尋找字符串中的第一個空格時沒有找到正確的位置,下面是解決方法,需要的朋友可以參考下2020-02-02Python 實現(xiàn)圖片色彩轉(zhuǎn)換案例
我們在看動漫、影視作品中,當人物在回憶過程中,體現(xiàn)出來的畫面一般都是黑白或者褐色的。本文將提供將圖片色彩轉(zhuǎn)為黑白或者褐色風格的案例詳解,感興趣的小伙伴可以了解一下。2021-11-11Python如何實現(xiàn)大型數(shù)組運算(使用NumPy)
這篇文章主要介紹了Python如何實現(xiàn)大型數(shù)組運算,文中講解非常細致,幫助大家更好的了解和學習,感興趣的朋友可以了解下2020-07-07scikit-learn線性回歸,多元回歸,多項式回歸的實現(xiàn)
這篇文章主要介紹了scikit-learn線性回歸,多元回歸,多項式回歸的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08詳解Python的迭代器、生成器以及相關的itertools包
這篇文章主要介紹了詳解Python的迭代器、生成器以及相關的itertools包,Iterators、Generators是Python的高級特性,亦是Python學習當中必會的基本知識,需要的朋友可以參考下2015-04-04python scipy求解非線性方程的方法(fsolve/root)
今天小編就為大家分享一篇python scipy求解非線性方程的方法(fsolve/root),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11Python中playwright啟動瀏覽器與常見運行方式詳解
Playwright是一個功能強大的工具,可以幫助開發(fā)人員自動化測試、網(wǎng)頁截圖、信息提取等任務,本文主要介紹了如何使用Playwright來啟動瀏覽器,感興趣的可以了解下2024-05-05python無限生成不重復(字母,數(shù)字,字符)組合的方法
今天小編就為大家分享一篇python無限生成不重復(字母,數(shù)字,字符)組合的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12