You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Mac OS下创建支持自动登录的.rdp文件及password 51:b字段生成方案

解决Mac上生成RDP文件密码字段及rdp://协议传密码的问题

一、生成RDP文件的password 51:b字段

Windows的CryptProtectData是系统特有的DPAPI加密,但RDP的密码字段遵循一套公开的加密规则,我们可以在Mac上手动实现这套规则来生成正确的51:b字段。

核心加密流程

RDP密码加密基于NTLM哈希、MD5和RC4,步骤如下:

  1. 将用户密码转换为UTF-16LE编码,计算其MD4哈希(即NTLM哈希)。
  2. 根据目标主机的名称/IP生成16字节的基础数据,计算其MD5哈希。
  3. 用步骤2的MD5哈希作为RC4密钥,加密步骤1的NTLM哈希,得到最终的RC4加密密钥。
  4. 用这个RC4密钥加密密码的UTF-16LE字节序列,将结果Base64编码后加上51:b:前缀,就是RDP文件中的密码字段值。

Swift代码实现示例

下面是完整的Swift实现,包含IP和主机名的处理:

import CommonCrypto

// 生成NTLM哈希(密码的MD4哈希,基于UTF-16LE)
func ntlmHash(from password: String) -> Data? {
    guard let utf16leData = password.data(using: .utf16LittleEndian) else {
        return nil
    }
    var hash = [UInt8](repeating: 0, count: Int(CC_MD4_DIGEST_LENGTH))
    utf16leData.withUnsafeBytes { bytes in
        CC_MD4(bytes.baseAddress, CC_LONG(utf16leData.count), &hash)
    }
    return Data(hash)
}

// 生成服务器标识的MD5哈希(支持主机名和IP)
func serverMD5(from serverIdentifier: String) -> Data? {
    var serverData: Data
    
    // 判断是否为IP地址
    let ipPattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
    if serverIdentifier.range(of: ipPattern, options: .regularExpression) != nil {
        // 解析IP为字节数组
        let ipParts = serverIdentifier.split(separator: ".").compactMap { UInt8($0) }
        guard ipParts.count == 4 else { return nil }
        serverData = Data(ipParts)
        // 补0到16字节
        while serverData.count < 16 {
            serverData.append(0)
        }
    } else {
        // 处理主机名:大写、截断为15字符、补0到16字节
        let upperName = serverIdentifier.uppercased()
        let truncatedName = upperName.prefix(15)
        serverData = Data(truncatedName.utf8)
        while serverData.count < 16 {
            serverData.append(0)
        }
    }
    
    // 计算MD5哈希
    var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
    serverData.withUnsafeBytes { bytes in
        CC_MD5(bytes.baseAddress, CC_LONG(serverData.count), &hash)
    }
    return Data(hash)
}

// RC4加密实现
struct RC4 {
    private var sBox: [UInt8] = Array(0...255)
    private var i: UInt8 = 0
    private var j: UInt8 = 0
    
    init(key: Data) {
        var j: UInt8 = 0
        for i in 0...255 {
            j &+= sBox[i] &+ key[Int(i % UInt8(key.count))]
            sBox.swapAt(i, Int(j))
        }
    }
    
    mutating func encrypt(data: Data) -> Data {
        var result = Data()
        for byte in data {
            i &+= 1
            j &+= sBox[Int(i)]
            sBox.swapAt(Int(i), Int(j))
            let keyByte = sBox[Int(sBox[Int(i)] &+ sBox[Int(j)])]
            result.append(byte ^ keyByte)
        }
        return result
    }
}

// 生成最终的RDP密码字段
func generateRdpPasswordField(password: String, serverIdentifier: String) -> String? {
    guard let ntHash = ntlmHash(from: password),
          let serverMd5 = serverMD5(from: serverIdentifier),
          ntHash.count == 16, serverMd5.count == 16 else {
        return nil
    }
    
    // 生成RC4加密密钥
    var keyGenerator = RC4(key: serverMd5)
    let rc4Key = keyGenerator.encrypt(data: ntHash)
    
    // 加密密码的UTF-16LE数据
    guard let passwordUtf16le = password.data(using: .utf16LittleEndian) else {
        return nil
    }
    var encryptor = RC4(key: rc4Key)
    let encryptedPassword = encryptor.encrypt(data: passwordUtf16le)
    
    // 生成最终字段
    return "51:b:\(encryptedPassword.base64EncodedString())"
}

使用时,调用generateRdpPasswordField(password: "yourPassword", serverIdentifier: "192.168.1.100")即可得到正确的密码字段值。


二、解决rdp://协议无法传递密码的问题

确实,macOS上的rdp://协议没有标准参数支持传递密码(出于安全和客户端实现限制),有两种替代方案:

方案1:生成.rdp文件并打开

将生成的密码字段写入.rdp文件,然后用命令行打开:

# 假设你已经生成了包含正确密码字段的myconnection.rdp
open myconnection.rdp

Microsoft Remote Desktop客户端会自动读取文件中的密码并填充。

方案2:用AppleScript控制客户端直接连接

通过AppleScript可以直接调用Microsoft Remote Desktop客户端,传递用户名、密码和主机信息并发起连接,无需生成文件:

tell application "Microsoft Remote Desktop"
    set newConn to make new desktop connection with properties {¬
        host:"192.168.1.100", ¬
        username:"yourUsername", ¬
        password:"yourPassword", ¬
        name:"Auto-Connect"}
    connect newConn
end tell

你也可以通过命令行执行这段脚本:

osascript -e 'tell application "Microsoft Remote Desktop" to connect (make new desktop connection with properties {host:"192.168.1.100", username:"yourUsername", password:"yourPassword"})'

注意:如果你的工具需要处理敏感密码,建议避免明文传递,不过AppleScript接口目前只接受明文密码,客户端会自行处理加密存储。


内容的提问来源于stack exchange,提问作者Stanislav

火山引擎 最新活动